# User

# Forgejo Next

https://next.forgejo.org, also known as Forgejo Next, is Forgejo's
official testing and demonstration instance. Everyone is encouraged to
use it to experiment with Forgejo, try out upcoming features, search for
bugs, or even casually host a project.

Please make sure you have a mirror or backup of any important data uploaded
to Forgejo Next! It won't be reset unless there is a need to, but this instance is
running potentially unstable code, so don't use it as your only storage for
irreplaceable valuable data.

# Comment on issues and pull requests via email

> [!note] Note
> This feature is not enabled by default; the [`[email.incoming].ENABLED`](../../admin/config-cheat-sheet/) setting must be set.

When receiving a notification, it is possible to reply to the email instead of using the web interface. The content of the reply will be stripped of the quoted text (lines starting with a greater than `>`) and used as the content of the comment. The attachments from the reply will be added as attachments to the comment.

It will be taken into account when receiving a notification email about:

- A newly created issue
- A newly created pull request
- A comment added to a specific line of code in a pull request review
- A comment on an existing issue
- A comment on an existing pull request

> [!note] Note
> The reply address contains a unique token to match the response with
> the issue or pull request and looks like this: `forgejo+ABCDE@example.com`.
> In some mail clients, such as Thunderbird, using this reply address may
> require using the `Reply List` button rather than the `Reply` button.

# Forgejo User Guide

These documents are targeted to people who are using Forgejo but are not
involved in running it on their machines.

- Getting started
  - [Your First Repository](./getting-started/first-repository/)
  - [The Basics of Issue Tracking](./getting-started/issue-tracking-basics/)
  - [Integrated Wiki](./getting-started/wiki/)
  - [Email Settings](./getting-started/email-settings/)
- Collaborating with Others
  - [Repository Activity](./collaboration/repository-activity/)
  - [Pull requests and Git flow](./collaboration/pull-requests-and-git-flow/)
  - [Invite Collaborators](https://docs.codeberg.org/collaborating/invite-collaborators/)
  - [Labels](./collaboration/labels/)
  - [Create and Manage an Organization](https://docs.codeberg.org/collaborating/create-organization/)
  - [Repository Permissions](./collaboration/repo-permissions/)
  - [Resolve Conflicts](https://docs.codeberg.org/collaborating/resolve-conflicts/)
  - [Projects / Kanban boards](./collaboration/project/)
  - [Citable Code](https://docs.codeberg.org/collaborating/citable-code/)
  - [Automatically Linked References](./collaboration/linked-references/)
  - [Blocking users](./collaboration/blocking-user/)
  - [Profile customization](./collaboration/profile/)
- Working with repositories
  - [Clone & Commit via Web](https://docs.codeberg.org/git/clone-commit-via-web/)
  - [Tags and Releases](./repository/releases/)
  - [Branch and tag protection](./repository/protection/)
  - Actions CI
    - [Overview](./actions/overview/)
    - [Quick start guide](./actions/quick-start/)
    - [Basic concepts](./actions/basic-concepts/)
    - [Advanced features](./actions/advanced-features/)
    - [Using Actions](./actions/actions/)
    - [Security](./actions/security/)
    - [Security of Pull Requests](./actions/security-pull-request/)
    - [Security OpenID Connect](./actions/security-openid-connect/)
    - [GitHub Actions differences](./actions/github-actions/)
    - [Reference](./actions/reference/)
  - [Git blame](./repository/blame/)
  - [README badges](./repository/readme-badges/)
  - [FUNDING files](./repository/funding/)
  - [Issue and Pull Request Templates](./repository/issue-pull-request-templates/)
  - [Merge Message templates](./repository/merge-message-templates/)
  - [Webhooks](./repository/webhooks/)
  - [Programming language detection](./repository/language-detection/)
- Search
  - [Code Search](./search/code-search/)
  - [Issue Search](./search/issue-search/)
- API
  - [Usage](./api/usage/)
  - [Forgejo versions](./api/versions/)
  - [Reference](https://codeberg.org/api/swagger)
- Git CLI
  - [AGit Workflow Usage](./git-cli/agit-support/)
  - [Push to create](./git-cli/push-to-create/)
- Authentication
  - [Generating an Access Token](https://docs.codeberg.org/advanced/access-token/)
  - [Access Token scope](./authentication/token-scope/)
  - [OAuth2, Client Types](./authentication/oauth2-provider/)
- Advanced Usage
  - [Comment on issues and pull requests via email](./incoming/)
  - [Migrating Repositories](https://docs.codeberg.org/advanced/migrating-repos/)
  - [Repository Mirrors](./repo-mirror/)
  - [Packages](./packages/)
  - [Forgejo Next](./forgejo-next/)
- See also
  - [Codeberg guide on Git](https://docs.codeberg.org/git/)
  - [Codeberg guide on Markdown](https://docs.codeberg.org/markdown/)
  - [Codeberg guide on Integrating with Matrix](https://docs.codeberg.org/integrations/matrix/)
  - [Codeberg guide on Integrating with Read the Docs](https://docs.codeberg.org/integrations/read-the-docs/)

# Repository Mirrors

Repository mirroring allows for the mirroring of repositories to and from external sources. You can use it to mirror branches, tags, and commits between repositories.

## Use cases

The following are some possible use cases for repository mirroring:

- You migrated to Forgejo but still need to keep your project in another source. In that case, you can simply set it up to mirror to Forgejo (pull), and all the essential history of commits, tags, and branches are available in your Forgejo instance.
- You have old projects in another source that you don't use actively anymore but don't want to remove for archiving purposes. In that case, you can create a push mirror so that your active Forgejo repository can push its changes to the old location.

## Pulling from a remote repository

For an existing remote repository, you can set up pull mirroring as follows:

1. Select **New Migration** in the **Create...** menu on the top right.
2. Select the remote repository service.
3. Enter a repository URL.
4. If the repository needs authentication, fill in your authentication information.
5. Check the box **This repository will be a mirror**.
6. Select **Migrate repository** to save the configuration.

The repository now gets mirrored periodically from the remote repository. You can force a sync by selecting **Synchronize Now** in the repository settings.

> [!important] Important
> You can only set up pull mirroring for repositories that don't exist yet on your instance.
> Once the repository is created, you can't convert it into a pull mirror anymore.

## Pushing to a remote repository

For an existing repository, you can set up push mirroring as follows:

1. In your repository, go to **Settings** > **Repository**, and then the **Mirror Settings** section.
2. Enter a repository URL.
3. (Optional) Enter a branch filter (comma separated branch names), or leave blank to push all branches.
4. If the repository needs authentication, expand the **Authorization** section and fill in your authentication information. Note that the requested **password** can also be your access token.
5. Select **Add Push Mirror** to save the configuration.

The repository now gets mirrored periodically to the remote repository. You can force a sync by selecting **Synchronize Now**. In case of an error, a message is displayed to help you resolve it.

> [!important] Important
> This will force push to the remote repository.
> This will overwrite any changes in the remote repository!

### Setting up a push mirror from Forgejo to GitHub

To set up a mirror from Forgejo to GitHub, you need to follow these steps:

1. Create a repository on GitHub using any name you wish (the name does not have to match the name of the Forgejo repository). Unlike Forgejo, GitHub does not support creating repositories by pushing to the remote. You can also use an existing remote repo if it has the same commit history as your Forgejo repo.
2. Create a [GitHub fine-grained personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token). While creating the token:
   1. In the **Repository access** section click the **Only select repositories** radio button.
   2. Click the **Select repositories** button, type the name of the repository you created, and click the checkbox next to its name to select it, then close the **Select repositories** drop-down list.
   3. In the **Permissions** box, click **Repositories** and then click the **Add permissions** button.
   4. Click the checkbox next to **Content** in the **Select repository permissions** drop-down list.
   5. If the repository contains (or could contain in the future) Actions workflows in the `.github/workflows` directory, click the checkbox next to **Workflows** in the **Select repository permissions** drop-down list.
3. Click the **Generate token** button, then copy and save the generated token.
4. In the settings of your Forgejo repo, fill in the **Git Remote Repository URL**: `https://github.com/<your_github_group>/<your_github_project>.git`.
5. Fill in the **Authorization** fields with your GitHub username and the personal access token as **Password**.
6. (Optional) Select `Sync when new commits are pushed` so that the mirror will be updated as soon as there are changes. You can also disable the periodic sync if you like.
7. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

### Setting up a push mirror from Forgejo to GitLab

To set up a mirror from Forgejo to GitLab, you need to follow these steps:

1. Create a [GitLab personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with _write_repository_ scope.
2. Fill in the **Git Remote Repository URL**: `https://<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
3. Fill in the **Authorization** fields with `oauth2` as **Username** and your GitLab personal access token as **Password**.
4. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

### Setting up a push mirror from Forgejo to Bitbucket

To set up a mirror from Forgejo to Bitbucket, you need to follow these steps:

1. Create a [Bitbucket app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) with the _Repository Write_ box checked.
2. Fill in the **Git Remote Repository URL**: `https://bitbucket.org/<your_bitbucket_group_or_name>/<your_bitbucket_project>.git`.
3. Fill in the **Authorization** fields with your Bitbucket username and the app password as **Password**.
4. Select **Add Push Mirror** to save the configuration.

The repository pushes shortly thereafter. To force a push, select the **Synchronize Now** button.

### Mirror via SSH

Forgejo supports the use of SSH as an authentication method for push mirrors.
You can enable this when adding a new push mirror; existing push mirrors cannot be configured to use SSH.
This feature is only available if Forgejo is able to find the `ssh` executable.
LFS over SSH protocol is not implemented in Forgejo, any LFS objects will not be mirrored.

To use SSH as an authentication method, select the **Use SSH authentication** option in the authorization tab when adding a new push mirror.
Make sure to use SSH-conform URLs for **Git Remote Repository URL**, which is e.g. `git@forgejo.org:<your_forgejo_group>/<your_forgejo_project>.git` for Forgejo (differs from the default authorization approach as mentioned above).
Make sure to not fill in the **Username** or **Password** input.
Forgejo generates an Ed25519 SSH key pair and saves it for you.

![The mirror settings is being shown and the SSH checkbox is ticked](../_images/user/repo-mirror/ssh_option.png)

After adding the push mirror, you can click the **Copy public key** link to copy the public key to your clipboard.

![The push mirror entry is shown](../_images/user/repo-mirror/push_mirror_with_ssh.png)

This public key can then be added as a deploy key on the target repository. How to add one varies by platform, but generally it should be an option in the repository's settings.
After adding the public key as the deploy key, you can go back to Forgejo and click the **Synchronize now** button and see that it works.

### Branch Filter

Forgejo will use `git push --mirror` when there is no Branch Filter specified.
When you enter a Branch Filter only branches matching the comma separated branch names will be pushed.
Branch names can include glob patterns. e.g. `feature/*`.
Spaces around branch names are trimmed.

Example: `main, feature/*` - this would push the `main` branch and `feature/add-foo`, but not `security/fix-alpha`.

# Forgejo Actions | Using Actions

Actions are reusable pieces of code that you can use in your CI workflows.

## Using Actions

For example, to check out the contents of the repository you can use the [`actions/checkout`](https://code.forgejo.org/actions/checkout) action. To use it, add the following step to a job in your workflow:

```yaml
- name: Check out the repository
  uses: https://data.forgejo.org/actions/checkout@v7
```

This specifies that this step `uses` the action `https://data.forgejo.org/actions/checkout`, version `v7`.

You can also specify an action like `uses: actions/checkout@v7`. In this case the runner will prefix it with the `DEFAULT_ACTIONS_URL` and proceed like normal. The `DEFAULT_ACTIONS_URL` is `https://data.forgejo.org/` by default, but it can be changed by the instance administrator. For this reason **it is strongly recommended to use fully qualified URLs**. In this guide we will use the shorter notation for brevity.

You can also load an action from a local directory instead of a remote repository. This is called a [local action](#local-actions).

### Action inputs

An Action can have parameters, called `input`s. You can usually read which inputs an action has in its README. To specify the inputs for an action, you can use the `with` key like so:

```yaml
- name: Check out the repository
  uses: actions/checkout@v7
  with:
    ref: my-feature-branch
    path: some/subdirectory
    show-progress: true
```

### Action dependencies

Since Actions are simply scripts run in your CI workflow, they usually have dependencies. For example, the `actions/checkout` action depends on NodeJS. If it is not present the action will fail. When using non-standard container images it is important to check that the required dependencies are present. If they are not you can try these fixes:

- Find a container image that does have the dependencies pre-installed.
- Install the dependencies _during_ the workflow, by using a package manager.
- Find an alternative action that doesn't have the dependencies.

### Local actions

If an action's `uses` statement starts with `./` it will be loaded from the specified local directory, instead of being cloned from a remote repository. The layout and content of the actions directory will be exactly the same as with a remote action.

[Take a look at the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-local-action/).

The action is taken from a local directory, from the perspective of the runner. This means you will typically need to have a checkout action before being able to use a local action.

## Creating your own actions

It is fairly simple to create your own actions. An action is really just a directory with an `action.yml` in it. There are three types of actions, `node`, `docker` and `composite`.

These types of actions are both explained further below.

> [!tip] Tip
> When first developing an action, it is useful to load it as a [local action](#local-actions) for testing.

### `action.yml`

The `action.yml` file contains all the metadata for the action. It decides the name, description, inputs, outputs and how to run the action.

Take a look at this simple example:

```yaml
name: 'Example Action'
author: 'Me, I wrote it'
description: |
  A simple action that just takes some input and writes it to a file.

inputs:
  message:
    description: 'The message to be stored'
    default: 'default message'

outputs:
  time:
    description: 'The time when the action was run'

runs: ...
```

The content of the `runs` key determines what type of action it is.

### `node` actions

Node actions are executed using NodeJS. NodeJS will _not_ automatically be installed, so if it is not present in the container image the action will fail.

Node actions have the following information in the `runs` key:

```yaml
runs:
  using: 'node20'
  main: 'index.js'
```

The `main` key determines the entry point for execution.

For more information about writing node actions, check [the GitHub documentation](https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-javascript-action). Keep in mind that certain details differ between GitHub and Forgejo.

### `docker` actions

Docker actions are executed using a container engine. They can only be used on runners that provide one, like Docker or Podman.

Docker actions have the following information in the `runs` key:

```yaml
runs:
  using: 'docker'
  image: 'Containerfile'
  args:
    - ${{ inputs.message }} # This also needs to be defined in the inputs section above.
```

The `image` key points at the `Containerfile` that should be used to build the image that will be run. The Containerfile contains the configuration for the base image, as well as an entrypoint. Arguments can be passed with the `args` key.

For more information about writing Docker actions, check [the GitHub documentation](https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action). Keep in mind that certain details differ between GitHub and Forgejo.

### `composite` actions

Composite actions are simply a series of steps from a normal workflow, packaged as an action for easier reuse.

Composite actions have the following information in the `runs` key:

```yaml
runs:
  using: 'composite'
  steps:
    - name: Print message
      run: echo "$MESSAGE"
      shell: bash
      env:
        MESSAGE: ${{ inputs.message }} # This also needs to be defined in the inputs section above.

    - name: Some other step
      ...
```

Composite actions can use other actions just like normal workflows. You can also write scripts, commit them to the action repository, and then use them in the action by calling them with a `run` key.

# Forgejo Actions | Advanced features

This page covers the more advanced features of Forgejo Actions.

## pre-commit hook

A [`pre-commit` hook](https://pre-commit.com/) is provided to assist in
validating Forgejo Actions files in your projects.
See [pre-commit.com](https://pre-commit.com/) for detailed instructions on how
to set it up, as well as more details on how it works, and then add this to
your `.pre-commit-config.yaml` file:

```yaml
repos:
  - repo: https://code.forgejo.org/forgejo/runner
    rev: v11.1.1
    hooks:
      - id: forgejo-runner-validate
```

## Artifacts

`Artifacts` let you persist data after a job has completed. An artifact is a file or collection of files produced during a workflow run.

This has two main uses:

- Storing non-permanent data after a workflow finishes, like test results.
- Storing data generated in one job, so another job later in the same workflow can use it.

Artifacts expire after a delay of 90 days by default, but this may be changed by the instance admin.

The artifacts created by a workflow can be downloaded from the web
interface that shows the details of the jobs for a workflow.

![download artifacts](../../_images/user/actions/actions-download-artifact.png)

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-artifacts/.forgejo/workflows)
based on the [upload-artifact](https://code.forgejo.org/actions/upload-artifact) action and
the [download-artifact](https://code.forgejo.org/actions/download-artifact) action.

> [!note] Note
> The name of an artifact is limited to 255 characters.

> [!note] Note
> It is not currently possible to access artifacts from other workflow runs within Forgejo [#8004](https://codeberg.org/forgejo/forgejo/issues/8004).

> [!note] Note
> When using upload and download actions you must use v3 or patched v4 as demonstrated [here](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-artifacts-v4/.forgejo/workflows/test.yml).

## Uploading to a package registry

Forgejo offers built-in [package registries](../../packages/). You can use these to publish packages for various package managers, or just [generic](../../packages/generic) build output.

## Cache

You can use the `Cache` to reuse commonly used files from previous workflow runs, like package downloads or intermediary build results.

Some Actions will automatically use the cache. For
instance the [setup-go](https://code.forgejo.org/actions/setup-go) action will do that by default to save downloading and compiling packages found in `go.mod`.

It is also possible to explicitly control what is cached (and when)
by using the [actions/cache](https://code.forgejo.org/actions/cache) action.
Caching makes no guarantees that a cache will be populated, even when two
`jobs` are run in sequence. If you want to reliably exchange information
between `jobs`, see [`artifacts`](#artifacts).

See also the [set of examples](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-cache/.forgejo/workflows/).

> [!note] Note
> [actions/cache](https://code.forgejo.org/actions/cache) will use `zstd` if present when compressing files to be sent to the cache.
> It is faster than the default compression.
> A container which does not have `zstd` installed _will not_ be able to decompress the cache and will continue without.
> In general it is recommended to ensure that if you're using `zstd` in one place in your project, you use it everywhere.

> [!note] Note
> If the runner is not configured to provide a cache,
> [actions/cache](https://code.forgejo.org/actions/cache) will fail with the following error:
> `Cache action is only supported on GHES version >= 3.5`.

### Caching with Podman

Since version 5, when used with [Pasta](https://passt.top/), which is the default, Podman copies the IP of the primary interface of the host into containers. Therefore, it is not possible to connect to the host using the host's primary IP address, which is the method used by `actions/cache`. It manifests in errors like `::warning::Failed to restore: getCacheEntry failed: connect ECONNREFUSED 192.168.124.60:41275`.

There are multiple options to deal with it.

One option is to configure Podman not to use the host's primary IP address for containers; see the article [Podman 5.0 breaking changes in detail](https://blog.podman.io/2024/03/podman-5-0-breaking-changes-in-detail/), section "Pasta default for rootless networking".

Another option is to change the Forgejo Runner configuration to use the "magic" host name `host.containers.internal`:

```yaml
cache:
  proxy_port: 4000
  actions_cache_url_override: 'http://host.containers.internal:4000'
```

However, that is only an option if a particular Forgejo Runner instance only uses Podman as a back-end and not, for example, Podman and LXC, because all back-ends would receive `http://host.containers.internal:4000` as cache URL.

## Services

Services like databases can be started from container images and are then available to all steps in a particular job:

```yaml
jobs:
  my-job:
    runs-on: docker
    services:
      pgsql:
        image: postgres:15
        env:
          POSTGRES_DB: test
          POSTGRES_PASSWORD: postgres
    ...
```

A container with the specified `image:` is run before the job starts and is terminated when it completes. The job can address the service using its name, in this case `pgsql`.

The IP address of `pgsql` is on the same [network](https://docs.docker.com/engine/reference/commandline/network/) as the container running the **steps** and there is no need for port binding. The `postgres:15` image exposes the PostgreSQL port 5432 and a client will be able to connect as [shown in this example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-service/.forgejo/workflows/test.yml).

> [!note] Note
> Services are only supported with the `docker` backend, not with the `lxc` backend.

> [!note] Note
> Services require [`[container].network`](../../../admin/actions/runner-installation/#configuration)
> to be empty so that a new network can be temporarily created to connect the container running the workflow with the services.

See the [reference](../reference/) for the list of supported options. There are also a number of [examples that illustrate the configuration and usage of services](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-service/.forgejo/workflows/).

## Grouping of log messages

To create an expandable section of the logs, use `##[group]` followed by a headline and end the section with `##[endgroup]`. Expansions can be nested.

```yaml
jobs:
  fold-example:
    runs-on: docker
    steps:
      - name: Expandable log lines
        run: |
          echo "##[group]Headline"
          echo "Initially, this text is not visible"
          echo "##[group]Subheadline"
          echo "This text isn't even visible after the first expansion! "
          echo "##[endgroup]"
          echo "This is still hidden at first"
          echo "##[endgroup]"
          echo "This is never hidden."
```

As can be seen in the example, the `##[endgroup]` lines are not shown themselves, but are counted in the line numbers.

![grouped log message](../../_images/user/actions/grouped_log_message.png)

## Workflow Run Prioritization

By default, Forgejo Actions jobs are run in the order they arrive. However, Forgejo offers the ability to manually mark entire workflow runs as prioritized. Prioritized workflow runs are executed before all others.

![List of workflow runs with one that is prioritized](../../_images/user/actions/run-prioritization.png)

Workflow runs can be prioritized by clicking on _Options_ in the workflow run list followed by _Prioritize run_. If a run should no longer be prioritized, the flag can be cleared by clicking on _Options_ followed by _Do not prioritize run_.

When a workflow run is prioritized, Forgejo will first run the jobs that belong to that particular workflow run. When multiple runs are prioritized, Forgejo will run them in the order they arrived, not in the order they have been prioritized.

Prioritization is best-effort. That means that it may not have an effect in some circumstances, for example, when a workflow run is already running. Also, the run order is currently not exposed over the HTTP API or in webhook messages. That means that external systems cannot participate.

## Job Summaries

A job summary is a composition of the summaries produced by each step. The content (Markdown) is rendered on top of the log output as soon as it is appended by a step.

![actionSummarySample](../../_images/user/actions/actions-step-summary.png)

> [!note] Note
> Each step can append up to 1MiB of Markdown. Surplus content is automatically truncated.

Each individual step of a Forgejo Actions job can render additional Markdown content. Using [Mermaid](https://mermaid.ai/open-source/intro/syntax-reference.html) and [KaTeX](https://katex.org/) is also supported.

Creating a summary is done by writing it into the file behind `FORGEJO_STEP_SUMMARY`. This example can be produced by:

````yml
on:
  push:
    branches:
      - '*'

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Run tests
        run: |
          # This application writes into $GITHUB_STEP_SUMMARY which is respected here as well
          dotnet run -c Release --project "./tests/UnitTests/UnitTests.csproj"

      - name: Add simple mermaid chart
        run: |
          cat << 'EOF' >> "$FORGEJO_STEP_SUMMARY"

          Mermaid's official [git graph sample](https://mermaid.ai/open-source/syntax/gitgraph.html)

          ```mermaid
          gitGraph
             commit
             commit
             branch develop
             commit
             commit
             commit
             checkout main
             commit
             commit
          ```

          EOF
````

# Forgejo Actions | Basic concepts

This page explains the basic concepts you'll encounter when using Forgejo Actions.

All Forgejo Actions workflows are defined in workflow files. These are `.yaml` files stored in the `.forgejo/workflows` directory of a repository. Below is an example of a simple workflow file:

## Basic syntax

```yaml
on: [push]
jobs:
  print-content:
    runs-on: docker
    steps:
      - name: checkout code
        uses: actions/checkout@v7
      - name: list directory contents
        run: ls -la
```

The `on` key specifies which events the workflow should trigger on. In this case it triggers on any push operation. You can narrow this down to specific branches, or trigger on other events like issues or pull requests.

The `jobs` key contains the jobs that are part of this workflow. A `job` is one unit of work that will be handed out to a runner. A workflow may contain multiple jobs. By default, these will run independently from one another.

This workflow contains one job, called `print-content`. The `runs-on` key specifies the label of the runner the workflow will run on. This job will only run on runners with the `docker` label.

A job consists of one or more steps. The steps are executed in order. If one step fails, the job will clean up and no further steps will be run.

The first step, `checkout code`, `uses` an action to check out the contents of the repository into the working directory. Actions are reusable procedures for doing something in your CI workflow. [Read more about actions](#actions).

The second step, `list directory contents`, `run`s the provided command in the shell of the job. In this case it lists the contents of the working directory. The output of this command will appear in the job logs.

That concludes the basic syntax of a workflow file. For a complete definition of the syntax of a workflow file, see the [workflow reference](../reference/).

## Expressions

In a workflow file you may use expressions. These look like `${{ ... }}`. Expressions are evaluated by the runner when executing a workflow. In certain fields of the workflow file, like `if: `, the `${{ }}` is implicit and can be stripped.

> [!note] Note
> Implicit type conversions with booleans may be difficult to figure out when assigned to actions inputs or output values.
> It is easier for debugging to use explicit strings (e.g. 'yes' or 'no') instead of true/false or 'true'/'false'.

[Check out the expression example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-expression/.forgejo/workflows/test.yml)

### Variables

You can use variables to read information from the workflow context. There are many different variables available. For example, the expression `${{ forgejo.ref_name }}` would be substituted with the name of the branch the workflow is running for.

For a complete list of the values available in the workflow context, see the [workflow reference](../reference/#context-reference).

### Literals

You may use the following types of literals in expressions:

- boolean: true or false
- null: null
- number: any number format supported by JSON
- string: enclosed in single quotes

### Logical operators

You may use the following logical operators in expressions:

| Operator | Description           |
| -------- | --------------------- |
| `( )`    | Logical grouping      |
| `[ ]`    | Index                 |
| `.`      | Property de-reference |
| `!`      | Not                   |
| `<`      | Less than             |
| `<=`     | Less than or equal    |
| `>`      | Greater than          |
| `>=`     | Greater than or equal |
| `==`     | Equal                 |
| `!=`     | Not equal             |
| `&&`     | And                   |
| `\|\|`   | Or                    |

> [!note] Note
> String comparisons are case insensitive.

### Conditionals

You may use these functions in `if:` conditionals on jobs and steps.

- `success()`. returns true when none of the previous jobs/steps have failed or been cancelled.
- `always()`. causes the job/step to always execute, and returns true, even when cancelled. If you want to run a job/step regardless of its success or failure, use the recommended alternative: `'!cancelled()'` (the expression must be enclosed by quotes to not be interpreted as YAML tag).
- `failure()`. returns true when any previous step/job has failed.

### Functions

You may use these functions in expressions:

- `contains( search, item )`. Returns `true` if `search` contains `item`. If `search` is an array, this function returns `true` if the `item` is an element in the array. If `search` is a string, this function returns `true` if the `item` is a substring of `search`. This function is not case sensitive. Casts values to a string when comparing.
- `startsWith( searchString, searchValue )`. Returns `true` when `searchString` starts with `searchValue`. This function is not case sensitive. Casts values to a string.
- `endsWith( searchString, searchValue )`. Returns `true` if `searchString` ends with `searchValue`. This function is not case sensitive. Casts values to a string.
- `format( string, replaceValue0, replaceValue1, ..., replaceValueN)`. Replaces values in the `string`, with the variable `replaceValueN`. Variables in the `string` are specified using the `{N}` syntax, where `N` is an integer. You must specify at least one `replaceValue` and `string`. Escape curly braces using double braces.
- `join( array, optionalSeparator )`. The value for `array` can be an array or a string. All values in `array` are concatenated into a string. If you provide `optionalSeparator`, it is inserted between the concatenated values. Otherwise, the default separator `,` is used. Casts values to a string.
- `toJSON(value)`. Returns a pretty-print JSON representation of `value`.
- `fromJSON(value)`. Returns a JSON object or JSON data type for `value`. You can use this function to provide a JSON object as an evaluated expression or to convert environment variables from a string.
- `hashFiles(pattern[, ...])`. Returns a hash (e.g. `39b35c59b92fdb3024d09a4f2f960d123ba59c3d531569f5195b65fbf0a40161`) based on the concatenated content of all the files matching the [patterns](https://www.npmjs.com/package/@actions/glob#patterns) given in argument. Returns an empty string if the files are not found. It is a JavaScript function executed in the container running the action and requires [Node.js](https://nodejs.org/) to be installed.
- `case(predicate1,value1[,predicateN,valueN]...,default)`. Returns the value of the first matching `predicate` otherwise returns the `default` value. `predicate`s must be booleans. The `default` argument must always be present and there must be an odd number of arguments. `float` values can have the decimal point dropped.

Some of these functions cast values to strings in order to compare them. The following conversions are used:

| Input type | Output result                                 |
| ---------- | --------------------------------------------- |
| `null`     | `''` (empty string)                           |
| booleans   | `'true'` or `'false'`                         |
| numbers    | decimal format, exponential for large numbers |
| arrays     | arrays are never converted to strings         |
| objects    | objects are never converted to strings        |

## Secrets

A repository, a user or an organization can hold secrets, a set of key/value pairs that are stored encrypted in the Forgejo database and revealed to the workflows as `${{ secrets.KEY }}`. They can be defined from the web interface:

- in `/org/{org}/settings/actions/secrets` to be available in all the repositories that belong to the organization
- in `/user/settings/actions/secrets` to be available in all the repositories that belong to the logged in user
- in `/{owner}/{repo}/settings/actions/secrets` to be available to the `workflows` of a single repository

![add a secret](../../_images/user/actions/secret-add.png)

Once the secret is added, its value cannot be changed or displayed.

![secrets list](../../_images/user/actions/secret-list.png)

## Custom variables

A repository, a user or an organization can hold variables, a set of key/value pairs that are stored in the Forgejo database and available to the workflows as `${{ vars.KEY }}`. They can be defined from the web interface:

- in `/org/{org}/settings/actions/variables` to be available in all the repositories that belong to the organization
- in `/user/settings/actions/variables` to be available in all the repositories that belong to the logged in user
- in `/{owner}/{repo}/settings/actions/variables` to be available to the `workflows` of a single repository

![add a variable](../../_images/user/actions/variable-add.png)

After a variable is added, its value can be modified.

![variables list](../../_images/user/actions/variable-list.png)

### Name constraints

The following rules apply to variable names:

- Variable names can only contain alphanumeric characters (`[a-z]`, `[A-Z]`, `[0-9]`) or underscores (`_`). Spaces are not allowed.
- Variable names must not start with the `FORGEJO_`, `GITHUB_` or `GITEA_` prefix.
- Variable names must not start with a number.
- Variable names are case-insensitive.
- Variable names must be unique at the level they are created at.
- Variable names must not be `CI`.

### Precedence

A variable found in the settings of the owner of a repository (organization or user) has precedence over the same variable found in a repository.

## Automatic token

At the start of each workflow, a unique authentication token is automatically created and destroyed when it completes. It can be used to read the repositories associated with the workflow, even when they are private. It is available:

- in the environment of each step as `FORGEJO_TOKEN`
- as `forgejo.token`
- as `env.FORGEJO_TOKEN`
- as `secrets.FORGEJO_TOKEN`

To help with reusing actions and workflows originally developed for GitHub Actions, they are also available under the following names:

- `GITHUB_TOKEN` == `FORGEJO_TOKEN`
- `github.token` == `forgejo.token`
- `env.GITHUB_TOKEN` == `env.FORGEJO_TOKEN`
- `secrets.GITHUB_TOKEN` == `secrets.FORGEJO_TOKEN`

This token can only be used for interactions with the repository of the project and any attempt to use it on other repositories, even for creating an issue, will return a 404 error. If additional permissions are required, using an [Authorized Integration](../../authorized-integrations/#forgejo-actions-local) is recommended.

This token also has write permission to the repository and can be used to push commits or use API endpoints such as creating a label or merge a pull request.

In order to avoid infinite recursion, no workflow will be triggered as a side effect of a change authored with this token. For instance, if a branch is pushed to the repository and there exists a workflow that is triggered on push events, it will not fire.

A workflow triggered by a `pull_request` or `pull_request_target` event from a forked repository is an exception: in that case the token does not have write permissions to the repository. The pull request could contain an untested or malicious workflow.

> [!note] Note
> In the case of a `pull_request_target` the risk is mitigated and the automatic token may be given write permissions in future versions of Forgejo.

## Actions

An `action` is a reusable procedure for accomplishing something in your CI workflow.

### Using actions

To use an action, specify the `uses:` key in a step like this:

```yaml
- name: Checkout the repository
  uses: actions/checkout@v7
  with:
    ref: 'main'
```

The `uses` key contains the name of the repository the action is stored in. You can specify it in shorthand like above, in which case it will be prefixed with the `DEFAULT_ACTIONS_URL`. You can also specify the full url: `uses: https://codeberg.org/username/yourcustomaction`.

The `with` key contains parameters to be passed to the action. Which parameters are available depends on which action you are using. You can find in the documentation of the action you are using.

### Creating actions

An action is a repository that contains the equivalent of a function in any programming language. It comes in two flavors, depending on the file found at the root of the repository:

- **action.yml:** describes the inputs and outputs of the action and the implementation. See [this example](https://code.forgejo.org/actions/setup-forgejo/src/branch/main/action.yml).
- **Dockerfile:** if no `action.yml` file is found, it is used to create an image with `docker build` and run a container from it to carry out the action. See [this example](https://code.forgejo.org/forgejo/test-setup-forgejo-docker) and [the workflow that uses it](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-docker-action). Note that files written outside of the **workspace** will be lost when the **step** using such an action terminates.

Just as any other program or function, an action has pre-requisites to successfully be installed and run. When looking at reusing an existing action, this is an important consideration. For instance [setup-go](https://code.forgejo.org/actions/setup-go) depends on NodeJS during installation.

To read more about creating your own actions, see the [actions guide](../actions/#creating-your-own-actions).

# Forgejo Actions | GitHub Actions

If you are familiar with GitHub Actions, you'll likely recognize many things in Forgejo Actions. Forgejo Actions is designed to be familiar to users of GitHub Actions, but it is **not designed to be compatible**.

## Familiarity instead of compatibility

Forgejo Actions is built on top of several existing technologies that were built to emulate GitHub Actions. For this reason, Forgejo Actions will seem very similar to GitHub Actions. Many features are (nearly) identical: workflows defined by YAML files in the repository, a swarm of runners executing actions inside docker containers, the ability to reuse workflows with Actions.

However, GitHub has different constraints when designing GitHub Actions. Because of this, they are able to add some features that are not a great fit for Forgejo Actions. On the other hand, Forgejo Actions has some features that GitHub Actions doesn't have, like running workflows in LXC containers.

Overall, many features from GitHub Actions translate one-to-one to Forgejo Actions. It is mostly the small differences that mean we can't claim to be 100% compatible. These small things are also usually the most difficult and least useful for our users to implement. And, since GitHub continues development on GitHub Actions, keeping up with all the changes would be impractical.

For all of these reasons, Forgejo Actions strives for _familiarity_ instead of _compatibility_. We want users of GitHub actions to feel familiar using Forgejo Actions, even if there are some small changes here and there. Workflows should work with some minimal changes.

If there is a feature you are missing, you are welcome to [discuss it](https://code.forgejo.org/forgejo/runner/issues), but keep in mind this philosophy.

## Known list of differences

> This list may be incomplete. If you find a notable difference, you can [edit this page](https://codeberg.org/forgejo/docs/src/branch/next/docs/user/actions/github-actions.md).

- The default environment is very different. Most Forgejo Runners use a Debian bookworm image with just node.js by default, while GitHub uses a larger `ubuntu` image.
- Some keys in the `github` context are missing.
- Certain subkeys on the `job` key in workflow files are ignored, like `permissions`, and `continue-on-error`.
- Enabling [OIDC ID token generation](../security-openid-connect/) uses the [`enable-openid-connect`](../reference/#enable-openid-connect) key in the workflow file instead of `permissions: id-token: write`

# Forgejo Actions | User guide

`Forgejo Actions` is a Continuous Integration system built into Forgejo. It lets you automatically build, lint, test, deploy or publish your software.

## Quick start

If you want a quick way to get started with an example, check out the [quick start guide](../quick-start/).

## Workflows

Forgejo Actions workflows are defined using `.yaml` files in the `.forgejo/workflows` directory of the repository. The workflow file describes the events that should trigger the workflow, and which jobs need to be executed.

> [!note] Note
> In the absence of a `.forgejo/workflows` directory, workflows will be looked up in the `.github/workflows` directory, if it exists.

For more information about how to write workflow files, check out the [basic concepts](../basic-concepts/). There is also a guide to the more [advanced features](../advanced-features/).

## Runners

Workflows are not executed by the Forgejo instance itself. Instead, they are handed out to runners, which perform the workflow and report back with the results.

In order for a workflow to run, an appropriate runner needs to be available to the repository. You can view the available runners in the repository settings, under Actions > Runners.

If the instance does not provide runners, or if you wish to use your own runners, check out the [Forgejo Runner installation guide](../../../admin/actions/runner-installation/) to see how to set up and connect your own runner.

## Troubleshooting

Learn the basics of [troubleshooting a failed workflow or action](../troubleshooting/).

## Security

Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).

A security audit of the [runner](https://code.forgejo.org/forgejo/runner) was conducted late 2024 (see [the penetration test report](https://code.forgejo.org/forgejo/nlnet-off-ngie-forgejo/raw/commit/69f76976cc6a62bc7a64f9f76e4be75eec3d608d/target/Forgejo%20penetration%20test%20report%202024%201.0.pdf) for more information). All issues found on that occasion were fixed in 2025.

## About GitHub Actions

If you are familiar with GitHub Actions, you'll likely recognize many things in Forgejo Actions. Forgejo Actions is designed to be familiar to users of GitHub Actions, but it is **not designed to be compatible**.

If you wish to migrate a workflow from GitHub Actions to Forgejo Actions, some minimal tweaking will most likely be necessary.

For a more detailed description of the differences between GitHub Actions and Forgejo Actions, check out the [GitHub Actions differences](../github-actions/) page.

## Alternative CI options

Forgejo Actions is a CI system built into Forgejo. There are also other CI systems available, which may be more suitable for certain use-cases. A list of these is available [in the forgejo-contrib repository](https://codeberg.org/forgejo-contrib/delightful-forgejo#ci-cd).

# Forgejo Actions | Quick start guide

This guide explains how to set up a simple Forgejo Actions workflow. The basic concepts are all mentioned here, and links to further information are provided.

## Setting up Actions

To get started we will have to do some setup:

1. First, verify that Actions is enabled for your repository. Visit your repository settings, and go to Units > Overview. Make sure the Actions checkbox is ticked. If it is not visible, your instance administrator has disabled Forgejo Actions.
   ![Enable Actions](../../_images/user/actions/enable-actions-unit.png)

2. Next, see if there is a runner available for the repository. Visit the repository settings, and go to Actions > Runners. If there are no runners listed, follow the [Forgejo Runner installation guide](../../../admin/actions/) to set up a runner, and return here when you're finished.
   ![The list of runners](../../_images/user/actions/repo-runner-list.png)

## Adding the workflow

Now we are ready to write a workflow file and add it to our repository:

3. Create a file `.forgejo/workflows/demo.yaml`, and add the following code:

```yaml
on: [push]
jobs:
  test:
    runs-on: docker
    steps:
      - run: echo All good!
```

This file describes a workflow. The workflow will trigger `on` a `push` event. The workflow contains one `job`, called `test`. This job will `run on` a runner with the label `docker`. If your runner (from step 2) has a different label, you should specify it here. The `test` job has one `step`, which is to simply `run` the command `echo All good!`.

Save the file and push it to the main branch of the repository.

![The demo.yaml workflow file](../../_images/user/actions/workflow-demo.png)

4. Go to the `Actions` tab of the repository. You should see your `demo.yml` workflow listed on the left. Because you pushed to a branch, the workflow has been triggered, and you can view the results of the run on the right.
   ![The workflow and run in the actions tab](../../_images/user/actions/runs-list.png)
   If you click on the run you'll get a more detailed view with the output from the runner:
   ![The runner output from the demo](../../_images/user/actions/actions-successful-demo.png)

If the run does not start and you get a warning that no matching runner is online, you have most likely forgotten to change the `runs-on` field in the workflow. Find the label for the runner you want to use in step 2, and edit your workflow file.
![No matching runner is online error](../../_images/user/actions/error-no-matching-runner.png)

Congratulations! You've just run your first workflow. Now you can expand your workflow to do all sorts of things, from building your code to sending it to a remote server. For most tasks you'll want to get the code into the workflow, so we'll cover that in the next section.

## Using an action to check out your code

An action is a reusable procedure for doing something in your CI workflow. In many ways, actions are similar to functions. To get the content of your repository into a workflow, we will be using the [`actions/checkout`](https://code.forgejo.org/actions/checkout) action. An action is simply a repository with some special files in it. For more information about how to use them and how to make your own, check out the [actions guide](../actions/).

5. Add the following lines to the `demo.yaml` file, below `- run: echo All good!`:

```yaml
- uses: actions/checkout@v7
- run: ls -la
```

Here we are adding two steps. The first step `uses` the action `actions/checkout`, specifically `v7`. We are not passing any parameters, as we just want to check out this repository. The second step simply `runs` the command `ls -la` to show us the content of the working directory.

Commit these changes and push them to the main branch of the repository.

6. Go to the actions tab and view the output the most recent run. You should see the two new steps.
   ![workflow run with checkout](../../_images/user/actions/actions-demo-checkout.png)

# Forgejo Actions | Reference

This page contains the complete reference for Forgejo Actions workflow files. For a more information on what the different parts of these files mean and how to use the syntax, check the [basic concepts](../basic-concepts/).

---

Jump to the [context reference](#contexts).

## Workflow Syntax

The syntax and semantics of the YAML file describing a `workflow` are _partially_ explained here. If something is not documented here, the [GitHub Actions documentation](https://docs.github.com/en/actions) may be helpful. However, GitHub Actions and Forgejo Actions _are not the same_ and things might not work right away.

Each chapter documents the function of one key in a workflow YAML file. Keys like `<job_id>` are placeholders for user-specified names.

### `defaults`

Set some defaults for all jobs in the workflow.

These global defaults can be overridden with the [per-job `defaults` block](#jobsjob_iddefaults), which in turn can be overridden by the corresponding option in each step.

```yaml
defaults:
  run:
    shell: bash
    working-directory: ./scripts
```

#### `defaults.run.shell`

Sets the default [`jobs.<job_id>.steps[*].shell`](#jobsjob_idstepsshell) for all jobs.

Can also be overridden per job: [`jobs.<job_id>.defaults.run.shell`](#jobsjob_iddefaultsrunshell)

#### `defaults.run.working-directory`

Sets the default [`jobs.<job_id>.steps[*].working-directory`](#jobsjob_idstepsworking-directory) for all jobs.

Can also be overridden per job: [`jobs.<job_id>.defaults.run.working-directory`](#jobsjob_iddefaultsrunworking-directory)

### `name`

An optional name for the workflow. This name is displayed in the actions tab. If omitted, the name of the workflow file will be used.

### `run-name`

An optional name for individual workflow runs generated from the workflow. This name is displayed in the list of workflow runs in a project's Actions tab. If omitted, or if it evaluates to an empty string, the default title is used instead (for example the commit message for `push` events, or the workflow `name` for manually dispatched runs).

`run-name` is evaluated once, server-side, when the run is created. It can use expressions, and can reference the [contexts](#contexts) `forgejo`, `github`, `inputs`, and `vars`:

```yaml
run-name: ${{ forgejo.actor }} deployed ${{ inputs.version }} to ${{ vars.DEPLOY_CLUSTER }}
```

### `on`

Workflows will be triggered `on` certain events with the following:

```yaml
on:
  <event-name>:
    <event-parameter>:
    ...
```

e.g. to run a workflow when branch `main` is pushed

```yaml
on:
  push:
    branches:
      - main
```

### `on.push`

Trigger the workflow when a commit or a tag is pushed.

If the `branches` event parameter is present, it will only be triggered if the commit is pushed to one of the branches in the list.

If the `paths` event parameter is present, it will only be triggered if the pushed commit modifies one of the path in the list.

If both `branches` and `paths` are present, the workflow will only be triggered if both match.

```yaml
on:
  push:
    branches:
      - 'mai*'
    paths:
      - '**/test.yml'
```

[Check out the push branches example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-push/.forgejo/workflows/test.yml).

If the `tags` event parameter is present, it will only be triggered if the the pushed tag matches one of the tags in the list.

```yaml
on:
  push:
    tags:
      - 'v1.*'
```

[Check out the push tags example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-tag/.forgejo/workflows/test.yml).

> [!note] Note
> Combining `tags` with `paths` or `branches` is not intended.

### `on.issues`

Trigger the workflow when an event happens on an issue, as specified with the `types` event parameter. It defaults to `[opened, edited]` if not specified.

- `opened` the issue was created.
- `reopened` the closed issue was reopened.
- `closed` the issue was closed or merged.
- `labeled` a label was added.
- `unlabeled` a label was removed.
- `assigned` an assignee was added.
- `unassigned` an assignee was removed.
- `edited` the title or body (description) of the issue were modified.

```yaml
on:
  issues:
    types: [opened, edited]
```

### `on.issue_comment`

Trigger the workflow when an event related to a comment happens on an issue or a pull request, as specified with the `types` event parameter. This event is different The `types` event parameter defaults to `[created, edited, deleted]` if not specified.

```yaml
on:
  issue_comment:
    types:
      - created
      - edited
      - deleted
```

> [!caution] Caution
> The event is only generated if the workflow exists on the default branch and the associated SHA is the tip of the default branch. It will not use a workflow found in a pull request.

### `on.pull_request`

Trigger the workflow when an event happens on a pull request, as specified with the `types` event parameter. It defaults to `[opened, synchronize, reopened]` if not specified.

- `opened` the pull request was created.
- `reopened` the closed pull request was reopened.
- `closed` the pull request was closed or merged.
- `labeled` a label was added.
- `unlabeled` a label was removed.
- `synchronize` the commits associated with the pull request were modified.
- `assigned` an assignee was added.
- `unassigned` an assignee was removed.
- `edited` the title or body (description) of the pull request were modified.

```yaml
on:
  pull_request:
    types: [opened, synchronize, reopened]
```

If the head of a pull request is from a forked repository, the secrets are not available and the automatic token only has read permissions.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-pull-request/.forgejo/workflows/test.yml).

### `on.pull_request_target`

Trigger the workflow when an event happens on a pull request, and execute the workflow in the context of the **target branch** of the pull request. Specifically, it is similar to the `on.pull_request` event with the following exceptions:

- secrets stored in the base repository are available in the `secrets` `context`, (e.g. `${{ secrets.KEY }}`).
- `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN` will be available in the environment if [`enable-openid-connect`](#enable-openid-connect) is set to `true` at the workflow or job level.
- the automatic token has write permission to the repository.
- the workflow runs in the context of the default branch of the base repository, meaning that:
  - changes to the workflow in the pull request will be ignored
  - the [actions/checkout](https://code.forgejo.org/actions/checkout) action will checkout the default branch instead of the content of the pull request

It is strongly recommended that workflows using `on.pull_request_target` do not interact with the untrusted code from the pull request, as it can be difficult to anticipate the security risks and side-effects. Although the workflow code itself is executed from the safe source of the target branch, its interactions with the untrusted code could compromise security. A known, non-exhaustive list of risks includes:

- All action steps are executed with the `FORGEJO_TOKEN` and `GITHUB_TOKEN` environment variables; interacting with untrusted code in the pull request may leak these tokens.
- Workflows with [`enable-openid-connect`](#enable-openid-connect) set to `true` at the workflow or job level will have the `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN` variables set; interacting with untrusted code in the pull request may leak these variables and request OIDC ID tokens.
- The `actions/checkout` action will persist the security token unless configured with `persist-credentials: false`; untrusted code in the pull request may retrieve the token from disk and lead to it being leaked.
- Secrets that are used in action steps, for example as command-line parameters or environment variables, may be available to environment inspection by untrusted code leading to the risk of secret compromise.
- An `actions/cache` step introduces an opportunity for executed untrusted code in a pull request to poison cache contents which may later be used in a workflow that is executed with higher-level permissions, such as access to more secrets.

The safest configuration is to not checkout the code of the pull request at all.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-pull-request/.forgejo/workflows/test.yml).

### `on.release`

Trigger the workflow when an event happens on release, as
specified with the `types` event parameter.

- `published` the release was created.
- `edited` the body, title or comments of the release were modified.
- `deleted` the release was deleted.

```yaml
on:
  release:
    types: [published, edited, deleted]
```

### `on.schedule`

The `schedule` event allows you to trigger a workflow at a scheduled time. When a workflow with a `schedule` event is present in the default branch, Forgejo will add a task to run it at the designated time. The scheduled workflows on other branches or pull requests are ignored.

The scheduled time is specified using the [POSIX cron syntax](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). See also the [crontab(5)](https://man.archlinux.org/man/crontab.5) manual page for a more information and some examples.

```yaml
on:
  schedule:
    - cron: '30 5,17 * * *'
```

[Check out the full example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-cron/.forgejo/workflows/test.yml).

By default, schedules are interpreted as UTC. The workflow in the example would be run each day at 05:30 and 17:30 UTC.

Optionally, a different time zone can be specified by supplying an [ID of an IANA time zone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones):

```yaml
on:
  schedule:
    - cron: '30 5,17 * * *'
      timezone: Europe/Berlin
```

This workflow would run each day year-round at 05:30 (04:30 UTC from November through March; 03:30 UTC from April
through October) and 17:30 (16:30 UTC or 15:30 UTC) in Berlin's local time.

> [!caution] Caution
> It is recommended not to schedule workflows during the time of day when daylight saving time changes
> occur. Workflows that are scheduled to run when the clock jumps forward **are skipped**. When the clock moves backward,
> workflows **run twice**.

Forgejo behaves differently than GitHub during daylight saving time changes for historical reasons. Forgejo supported
specifying time zones before GitHub did, although using a different syntax, and chose a different approach for dealing
with DST changes.

### `on.workflow_call`

The `workflow_call` event allows you to call a workflow from another workflow.

For example if the following workflow is found in `.forgejo/workflows/reusable.yml`:

```yaml
on:
  workflow_call:

jobs:
  callee:
    runs-on: docker
    steps:
      - run: echo OK
```

it can be called from another workflow as follows:

```yaml
on:
  push:

jobs:
  caller:
    uses: ./.forgejo/workflows/reusable.yml
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/test.yml).

### `on.workflow_call.inputs`

Inputs for a [`workflow_call`](#onworkflow_call) are declared in the `inputs` sub-key, where each sub-key itself is an input. Each of those inputs may have a `type`:

- `boolean`
- `number`
- `string`

Additionally, every input can be made `required`, given a human-readable `description`, and a `default` value.

```yaml
on:
  workflow_call:
    inputs:
      boolean:
        description: 'Boolean'
        required: false
        type: boolean
      number:
        description: 'Number'
        default: '100'
        type: number
      string:
        description: 'String'
        required: true
        type: string
```

Inputs then can be used inside the jobs with the `inputs` context:

```yaml
jobs:
  test:
    runs-on: docker
    steps:
      - run: echo ${{ inputs.number }}
```

Inputs are provided to a [`workflow_call`](#onworkflow_call) by using [`jobs.<job_id>.with`](#jobsjob_idwith).

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/reusable-layer-1.yml).

### `on.workflow_call.outputs`

Outputs for a [`workflow_call`](#onworkflow_call) are declared in the `outputs` sub-key:

```yaml
outputs:
  name_of_the_output:
    value: value_of_the_output
```

A concrete example looks like this:

```yaml
on:
  workflow_call:
    outputs:
      output1:
        value: ${{ jobs.callee.outputs.job-output }}

jobs:
  callee:
    runs-on: docker
    outputs:
      job-output: ${{ steps.stepwithoutput.outputs.myvalue }}
    steps:
      - id: stepwithoutput
        run: |
          echo "myvalue=outputvalue1" >> $FORGEJO_OUTPUT
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/reusable-layer-1.yml).

### `on.workflow_dispatch`

The `workflow_dispatch` events allows for manual triggering a workflow by either using the Forgejo UI, or the API with the `POST /repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches` endpoint. This event allows for inputs to be defined, which will get rendered in the Forgejo UI or read from the body of the API request.

Inputs are declared in the `inputs` sub-key, where each sub-key itself is an input. Each of those inputs need to have a `type`. These types can be:

- `choice`: A dropdown where the available options are defined as a list of strings with `options`
- `boolean`: A checkbox with the values of `true` or `false`
- `number`
- `string`

Additionally, every input can be made `required`, given a human-readable `description`, and a `default` value.

```yaml
on:
  workflow_dispatch:
    inputs:
      logLevel:
        description: 'Log Level'
        required: true
        default: 'warning'
        type: choice
        options:
          - info
          - warning
          - debug
      boolean:
        description: 'Boolean'
        required: false
        type: boolean
      number:
        description: 'Number'
        default: '100'
        type: number
      string:
        description: 'String'
        required: true
        type: string
```

Inputs then can be used inside the jobs with the `inputs` context:

```yaml
jobs:
  test:
    runs-on: docker
    steps:
      - run: echo ${{ inputs.logLevel }}
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-dispatch/.forgejo/workflows/test.yml).

### `enable-email-notifications`

Send an email notification when a workflow run fails.

```yaml
enable-email-notifications: true
```

The email notification is sent to the user who triggered the workflow run (for example, the author of a pull request, the user who pushed a commit), unless they disabled email notifications in their settings.

If a workflow run is not associated with a user like a scheduled workflow run, the notification will be sent to:

- The user who owns the repository unless they disabled email notifications in their settings.
- The organization that owns the repository unless there is no contact email in its settings.

> [!note] Note
> This setting has no effect on the Actions webhooks.

### `enable-openid-connect`

Allow all jobs in the workflow to generate OIDC compliant ID tokens.

```yaml
enable-openid-connect: true
```

Enabling this setting adds `ACTIONS_ID_TOKEN_REQUEST_URL` and `ACTIONS_ID_TOKEN_REQUEST_TOKEN` which can be used to generate an ID token.

See the [Security OpenID Connect documentation](../security-openid-connect/) for more information.

> [!note] Note
> This setting is disabled for `pull_request` events from forked repositories.

### `env`

Set environment variables that are available in the workflow in the `env` context and as regular environment variables.

```yaml
env:
  KEY1: value1
  KEY2: value2
```

- The expression `${{ env.KEY1 }}` will be evaluated to `value1`
- The environment variable `KEY1` will be set to `value1`

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-expression/.forgejo/workflows/test.yml).

### `concurrency`

The concurrency settings control the concurrent executions of a workflow when triggered by multiple distinct events.

If no concurrency settings are provided, Forgejo will automatically manage the concurrency of a workflow that is executed by the `on.push` and `on.pull_request` (synchronize) events in the following manner: whenever a new event triggers a new workflow invocation, any other invocations of the same workflow will be canceled automatically.

Some common usages of `concurrency` include:

- If you're performing checks, such as automated tests in a software project, then it can be desirable to ignore older changes and focus on checking the most recent changes. The default behavior, omitting `concurrency` from your workflow, is usually fine.
- If you're performing releases of artifacts from a branch, then you may want to ensure each event's workflow is completed without being superseded by a later event. [`concurrency.cancel-in-progress`](#concurrencycancel-in-progress) can be set to `false` to aid this case, and [`concurrency.group`](#concurrencygroup) can be omitted.
- If you're performing deployments, then using [`concurrency.group`](#concurrencygroup) can be important so that only a single deployment occurs at one time. [`concurrency.cancel-in-progress`](#concurrencycancel-in-progress) is typically set to `false` to avoid canceling an in-progress deployment.

Multiple jobs within a workflow are not affected by the `concurrency` setting. [`jobs.<job_id>.needs`](#jobsjob_idneeds) can be used to introduce ordering between different jobs, which can prevent them from running concurrently if desired.

### `concurrency.group`

An optional concurrency group for the workflow. A concurrency group is a string that is defined by the workflow. When a concurrency group is present, Forgejo will make a best-effort to ensure that only a single workflow with the same concurrency group is executed at one time. The [contexts](#contexts) `forgejo`/`forge`/`github`, `inputs`, and `vars` can be referenced. Concurrency groups are case insensitive.

When multiple workflows are triggered from events with a concurrency group, the [`concurrency.cancel-in-progress`](#concurrencycancel-in-progress) setting determines _how_ only a single workflow will execute; either cancelling previous workflow runs, or queuing behind them.

For example:

```yaml
on: [push]
concurrency:
  group: deploy-${{ forgejo.ref }}
jobs:
  # ...
```

In this example, the concurrency group will evaluate to a string like `deploy-refs/heads/main`. Only a single workflow with this concurrency group will execute at once, even if multiple pushes occur to the `main` branch.

Forgejo makes a **best-effort** to ensure that only a single workflow with the same concurrency group is executed at one time by dispatching tasks to runners only when Forgejo believes no other runner is executing the workflow. However, in exceptional circumstances this cannot be guaranteed.

### `concurrency.cancel-in-progress`

`cancel-in-progress` is a boolean setting indicating whether to cancel the execution of other workflows when new events trigger new workflows. Variables from the [contexts](#contexts) `forgejo`/`forge`/`github`, `inputs`, and `vars` can be referenced.

If the [`concurrency.group`](#concurrencygroup) setting is not provided, then `cancel-in-progress` behaves as follows:

- If omitted, it will default to `true` for `on.push` and `on.pull_request` (synchronize) events.
- If evaluated to `true`, then any previous invocation of the same workflow with the same branch will be canceled, allowing the newest event to be processed as soon as possible.
- If evaluated to `false`, then no concurrency management will occur. Multiple invocations of the workflow may run simultaneously, as long as the available runner instances have capacity to execute the workflows.

Example:

```yaml
on: [push]
concurrency:
  cancel-in-progress: false
```

If the [`concurrency.group`](#concurrencygroup) setting is provided, then `cancel-in-progress` behaves as follows:

- If omitted, it will default to `true` for `on.push` and `on.pull_request` (synchronize) events.
- If evaluated to `true`, then any previous invocation of any workflow in the repository with the same concurrency group will be canceled, allowing the newest event to be processed as soon as possible.
- If evaluated to `false`, then any previous invocation of any workflow in the repository with the same concurrency group will be executed before the newer invocation, allowing each workflow to complete in sequence. Note that exact sequential ordering is not guaranteed; while typically workflows that were triggered earlier will be executed earlier, there are some edge-cases where this does not occur. For example, a workflow may be "re-run" through the UI even if later workflows have already been queued or completed.

Example:

```yaml
on: [push]
concurrency:
  group: deploy-${{ forgejo.ref }}
  cancel-in-progress: true
```

As the value of `cancel-in-progress` can be evaluated with an expression, it can be used to make workflows sequential conditionally; for example, the below configuration would cancel most workflows on a push, but allow multiple simultaneous workflows on `main`:

```yaml
on: [push]
concurrency:
  cancel-in-progress: ${{ forgejo.ref_name != 'main' }}
jobs:
  # ...
```

### `jobs`

The list of jobs in the workflow. The key to each job is a `job_id` and its content defines the sequential `step`s to be run.

Each job runs in a different container and shares nothing with other jobs.

All jobs run in parallel, unless they depend on each other as specified with [`jobs.<job_id>.needs`](#jobsjob_idneeds).

### `jobs.<job_id>`

Specifies the id for the job. This is used in some places to uniquely identify the job.

### `jobs.<job_id>.runs-on`

Specifies the kind of machine that is needed to run the `job`. For instance `docker` in the following `workflow`

```yaml
---
jobs:
  test:
    runs-on: docker
```

means that the job will only be sent to a `runner` which has declared the `docker` label.

You may have labels to differentiate between:

- Different running environments (Docker, Podman, LXC, host, etc.)
- Different default images (ubuntu-latest, alpine-latest, etc.)
- Different architectures (arm, x86_64, etc.)
- Specific hardware installed on the runner (nvidia-gpu, big-ram, etc.)

The actual machine provided by the runner **entirely depends on how the runner was registered** (see the [Forgejo Actions administrator guide](../../../admin/actions/) for more information).

The list of available `labels` for a given repository can be seen in the `/{owner}/{repo}/settings/actions/runners` page.

![a list of runners, with their associated labels](../../_images/user/actions/list-of-runners.png)

If your job specifies a label for which no runner is online, the job cannot be executed and your pipeline will halt until a runner with a matching label comes online. You will be able to see this in the Actions tab of your repository.

`runs-on` is typically a required field. However, if a job defines [`jobs.<job_id>.uses`](#jobsjob_iduses) in order to reference a reusable workflow, then it is optional. See [`jobs.<job_id>.uses`](#jobsjob_iduses) for more information on this behaviour.

### `jobs.<job_id>.if`

When specified, the job is only run if the **expression** evaluates to true.

For instance:

```yaml
---
jobs:
  build:
    if: forgejo.ref == 'refs/heads/main'
    steps:
      - run: echo only run on main branch
```

### `jobs.<job_id>.needs`

Can be used to introduce ordering between different jobs by listing their respective `<job_id>`. All jobs listed here must complete successfully before this job is considered for execution.

`needs` can either be a single string, naming a single job as pre-requisite, or an array for specifying multiple jobs to run before this one.

For instance:

```yaml
---
jobs:
  lint:
    steps:
      - run: echo linting the code
  build:
    needs:
      - lint
    steps:
      - run: echo only run after linting
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-needs/.forgejo/workflows/test.yml).

### `jobs.<job_id>.timeout-minutes`

Set the number of minutes this job can be performed before it is canceled.

```yaml
jobs:
  test:
    runs-on: docker
    timeout-minutes: 1
    steps:
      - run: sleep 2m
      - run: echo will never run
```

### `jobs.<job_id>.outputs`

The `jobs.<job_id>.outputs` object is a key/value map of the output of the
corresponding job, defined by writing to `$FORGEJO_OUTPUT`. For instance:

```yaml
on: [push]
jobs:
  job1:
    runs-on: docker
    outputs:
      job1output: ${{ steps.step1.outputs.value }}
    steps:
      - id: step1
        run: |
          set -x
          echo "value=value1" >> $FORGEJO_OUTPUT
```

This output can then be used in jobs that run after it completes (i.e. that need it in the sense of [`jobs.<job_id>.needs`](#jobsjob_idneeds)). For instance:

```yaml
job2:
  needs: [job1]
  runs-on: docker
  steps:
    - run: |
        set -x
        test "${{ needs.job1.outputs.job1output }}" = "value1"
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-needs/.forgejo/workflows/test.yml).

### `jobs.<job_id>.uses`

Specifies the reusable workflow to call with a `workflow_call` event. `uses` can be specified in one of three supported formats:

- `./.forgejo/workflows/reusable.yml` -- refers to a workflow file within the same repository as the current workflow is defined.
- `some-org/some-repo/.forgejo/workflows/reusable.yml@main` -- refers to a workflow file within a different repository (`some-org/some-repo`), at a specified path and Git reference (`main`). The target repository must be a public repository.
- `https://example.com/some-org/some-repo/.forgejo/workflows/reusable.yml@main` -- refers to a workflow file hosted on a different Forgejo or GitHub instance. The target repository must be a public reposistory.

It is recommended that [`jobs.<job_id>.runs-on`](#jobsjob_idruns-on) is omitted when using `uses`, as this will allow Forgejo to perform workflow expansion. Workflow expansion results in the target workflow's jobs appearing in the UI as separate jobs. This provides an easier to understand experience for accessing the logs of each job, and permits the jobs to run on separate runners with their own `runs-on` fields. A workflow file hosted on a different Forgejo or GitHub instance is not supported by workflow expansion, and will automatically disable it.

`uses` is typically accompanied by [`jobs.<job_id>.with`](#jobsjob_idwith) and [`jobs.<job_id>.secrets`](#jobsjob_idsecrets) to provide information into the reusable workflow. See [`on.workflow_call`](#onworkflow_call) for more information on defining a workflow call.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/test.yml).

### `jobs.<job_id>.with`

A dictionary mapping the inputs of the `workflow_call` event to concrete values. See [`on.workflow_call.inputs`](#onworkflow_callinputs) for more information on how the inputs are declared in reusable workflow.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/test.yml).

### `jobs.<job_id>.secrets`

Controls the secrets provided to the reusable workflow being called. It can either be `secrets: inherit` in which case the reusable workflow will have access to the same secrets as the caller. Or it can be a dictionary mapping a selection of secrets. For instance in the following, the reusable workflow will get `keep_it_private` as the result of evaluating the expression `${{ secrets.secret }}`:

```yaml
on:
  push:

jobs:
  caller:
    runs-on: docker
    uses: ./.forgejo/workflows/reusable.yml
    secrets:
      secret: keep_it_private
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-workflow-call-expansion/.forgejo/workflows/test.yml).

### `jobs.<job_id>.name`

The name of the job. If not set, the name of the job defaults to `<job_id>`.
The expressions it contains will be interpolated. For instance if the variable `SOME` is set to `THING`, in the following:

```yaml
jobs:
  test:
    name: jobname-${{ vars.SOME }}
```

The name of the job will be `jobname-THING`.

### `jobs.<job_id>.enable-openid-connect`

Allow this job to generate OIDC compliant ID tokens.

```yaml
on:
  push:

jobs:
  caller:
    uses: ./.forgejo/workflows/reusable.yml
    enable-openid-connect: true
```

See also [enable-openid-connect](#enable-openid-connect).

> [!note] Note
> This setting takes precedence over the workflow level setting.

> [!note] Note
> This setting is disabled for `pull_request` events from forked repositories.

### `jobs.<job_id>.strategy.matrix`

If present, it will generate a matrix from the content of the object
and create one job per cell in the matrix instead of a single job.

For instance:

```yaml
jobs:
  test:
    runs-on: self-hosted
    strategy:
      matrix:
        variant: ['bookworm', 'bullseye']
        node: ['18', '20']
    steps:
      - uses: https://code.forgejo.org/actions/setup-node@v7
        with:
          node-version: '${{ matrix.node }}'
```

Will create four jobs where:

- `matrix.variant` = "bookworm" & `matrix.node` = "18"
- `matrix.variant` = "bookworm" & `matrix.node` = "20"
- `matrix.variant` = "bullseye" & `matrix.node` = "18"
- `matrix.variant` = "bullseye" & `matrix.node` = "20"

They each run independently and can use the `matrix` context to access these values, like in the `node-version` key in the snippet.

The `strategy.matrix` option can calculate the jobs to be created, typically by using the `fromJSON` evaluation function. The following snippet contains the same four job matrix defined above, but the matrix options can be defined by the `matrix-generator` job. The test job `needs` the `matrix-generator` job so that it is executed first and defines the job matrix:

```yaml
jobs:
  matrix-generator:
    runs-on: docker
    outputs:
      variants: ${{ steps.generate_matrix.outputs.variants }}
      nodes: ${{ steps.generate_matrix.outputs.nodes }}
    steps:
      - id: generate_matrix
        run: |
          echo 'variants=["bookworm", "bullseye"]' >> $GITHUB_OUTPUT
          echo 'nodes=["18", "20"]' >> $GITHUB_OUTPUT
  test:
    needs: matrix-generator
    runs-on: docker
    strategy:
      matrix:
        variant: ${{ fromJSON(needs.matrix-generator.outputs.variants) }}
        node: ${{ fromJSON(needs.matrix-generator.outputs.nodes) }}
    container:
      image: debian:${{ matrix.obj.variant }}
    steps:
      - uses: https://code.forgejo.org/actions/setup-node@v7
        with:
          node-version: '${{ matrix.node }}'
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/commit/b6591e2f71196b12f6e0851774f0bd6e2148ec18/.forgejo/workflows/actions.yml#L22-L37).

> [!note] Note
> If `jobs.<job_id>.name` is set, care must be taken that it evaluates to a unique name for each job created from the matrix,
> e.g., `name-${{ matrix.variant }}-${{ matrix.node}}` in the above example.

### `jobs.<job_id>.container.image`

- **Docker or Podman:**
  If the default image is unsuitable, a job can specify an alternate container image with `container:`, [as shown in this example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-container/.forgejo/workflows/test.yml). For instance the following will ensure the job is run using [Alpine 3.20](https://hub.docker.com/_/alpine/tags?name=3.20).

  > [!caution] Caution
  > When starting a container, Forgejo Runner does the equivalent of `docker run <image>`.
  > That means that container images, once downloaded, are **never updated**.

  > [!note] Note
  > Many actions require node to run. Using a custom container image that does not contain node may cause these actions to break.

  ```yaml
  runs-on: docker
  container:
    image: alpine:3.20
  ```

  It is generally recommended to pin image versions instead of relying on tags, for example, `debian:docker://node@sha256:sha256:91447bc57243b852a21e0ff3553f531f0d4b66257a564b106c79d9e00f3aa14e` instead of `debian:docker://node:lts`. That is a prerequisite for reproducible jobs.

- **LXC:**
  If the default [template and release](https://images.linuxcontainers.org/) specified by the runner are unsuitable, a job can specify an alternate template and release as follows.

  ```yaml
  runs-on: lxc
  container:
    image: debian:bookworm
  ```

### `jobs.<job_id>.container.entrypoint`

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

`entrypoint` takes a string array. If it is empty, the image's default entrypoint will be used (can be determined with `docker inspect <image> --format 'Entrypoint: {{.Config.Entrypoint}}'`). If it is not defined, then Forgejo Runner will set a custom entrypoint that keeps the container alive for the duration of a job.

`entrypoint` must be a process that continues to run for the life of the job container. If the process terminates before all steps are executed, the job will fail with an error. Steps in the job are executed by performing the equivalent of `docker exec` on the container, and do not invoke the entrypoint again.

Example of a custom `entrypoint`:

```yaml
on:
  push:
jobs:
  test:
    runs-on: docker
    container:
      image: node:lts
      entrypoint: ['/usr/bin/sleep', 'infinity']
    steps:
      - run: ps axf
```

`sleep infinity` ensures that the container keeps running.

Example that uses a container image's default entrypoint:

```yaml
on:
  push:
jobs:
  test:
    runs-on: docker
    container:
      image: alpine:3.23
      entrypoint: []
      tty: true
    steps:
      - run: ps
```

`alpine:3.23` has no entrypoint, only a default command, which is `/bin/sh`. `/bin/sh` would terminate immediately without a TTY. Therefore, `tty` has to be enabled to keep the container running.

### `jobs.<job_id>.container.env`

Set environment variables in the container.

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

### `jobs.<job_id>.container.init`

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

`init` is a boolean (default: `false`). If it is set to `true`, then an init process (PID 1) will be run inside the container that forwards signals and reaps processes.

### `jobs.<job_id>.container.credentials`

If the image's container registry requires authentication to pull the image, `username` and `password` will be used.
The credentials are the same values that you would provide to the `docker login` command. For instance:

```yaml
runs-on: docker
container:
  image: alpine:3.20
  credentials:
    username: 'root'
    password: 'admin1234'
```

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-container/.forgejo/workflows/test.yml)

### `jobs.<job_id>.container.tty`

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

`tty` is a boolean (default: `false`). If it is set to `true`, then a pseudo-TTY will be attached to the container.

### `jobs.<job_id>.container.volumes`

Set the volumes for the container to use, as if provided with the `--volume` argument of the `docker run` command.

> [!note] Note
> The `--volume` option is restricted to a list of volumes configured in the runner executing the task.
> See the [Forgejo Runner installation guide](../../../admin/actions/runner-installation/#configuration) for more information.

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-context/.forgejo/workflows/test.yml)

### `jobs.<job_id>.container.options`

A string of the following additional options, as documented [docker run](https://docs.docker.com/engine/reference/commandline/run/).

- `--volume`
- `--tmpfs`
- `--hostname` (except for Forgejo Runner 6.0.x and 6.1.x)
- `--memory` (requires runner 11.2.0 or greater)

> [!note] Note
> The `--volume` option is restricted to a list of volumes configured in the runner executing the task.
> See the [Forgejo Actions administrator guide](../../../admin/actions/) for more information.

> [!note] Note
> The value of `--memory` cannot be higher than the value set in the runner configuration file in `[container].options`, if any.

> [!note] Note
> Ignored if `jobs.<job_id>.runs-on` is an LXC container.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-service/.forgejo/workflows/test.yml)

### `jobs.<job_id>.services`

The map of services to run before the job starts and terminate when it completes. The key determines the name of the host where the
service runs. For instance:

```yaml
services:
  pgsql:
    image: postgres:15
    env:
      POSTGRES_DB: test
      POSTGRES_PASSWORD: postgres
steps:
  - run: PGPASSWORD=postgres psql -h pgsql -U postgres -c '\dt' test
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-service/.forgejo/workflows/test.yml)

### `jobs.<job_id>.services.<service_name>.cmd`

A list of command and arguments, equivalent to [[COMMAND] [ARG...]](https://docs.docker.com/engine/reference/commandline/run/).

### `jobs.<job_id>.services.<service_name>.credentials`

#### `jobs.<job_id>.services.<service_name>.credentials.username`

The username to authenticate with the registry where the image is located.

#### `jobs.<job_id>.services.<service_name>.credentials.password`

The password or access token to authenticate with the registry where the image is located.

### `jobs.<job_id>.services.<service_name>.entrypoint`

`entrypoint` takes a string array. If it is empty or undefined, the image's default entrypoint will be used (can be determined with `docker inspect <image> --format 'Entrypoint: {{.Config.Entrypoint}}'`).

Example of a custom `entrypoint`:

```yaml
on:
  push:
jobs:
  test:
    runs-on: debian-latest
    services:
      custom-entrypoint:
        image: data.forgejo.org/oci/postgres:18
        entrypoint: ['/bin/sh', '-c', 'POSTGRES_PASSWORD=mysecretpassword docker-entrypoint.sh postgres']
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 20
    steps:
      - run: |
          echo "mysecretpassword" | psql --user postgres --host custom-entrypoint -c "SELECT 1"
```

### `jobs.<job_id>.services.<service_name>.env`

Key/value pairs injected in the environment when running the container, equivalent to [`--env`](https://docs.docker.com/engine/reference/commandline/run/).

### `jobs.<job_id>.services.<service_name>.image`

The location of the container image to run.

### `jobs.<job_id>.services.<service_name>.init`

`init` is a boolean (default: `false`). If it is set to `true`, then an init process (PID 1) will be run inside the service container that forwards signals and reaps processes.

### `jobs.<job_id>.services.<service_name>.options`

A string of the following additional options, as documented [docker run](https://docs.docker.com/engine/reference/commandline/run/).

- `--volume`
- `--tmpfs`
- `--hostname`
- `--memory` (requires runner 11.2.0 or greater)
- `--health-cmd`
- `--health-interval`
- `--health-retries`
- `--health-timeout`
- `--health-start-period`
- `--no-healthcheck`

> [!note] Note
> The `--volume` option is restricted to a list of volumes configured in the runner executing the task.
> See the [Forgejo Runner installation guide](../../../admin/actions/runner-installation/#configuration) for more information.

> [!note] Note
> The value of `--memory` cannot be higher than the value set in the runner configuration file in `[container].options`, if any.

### `jobs.<job_id>.services.<service_name>.tty`

`tty` is a boolean (default: `false`). If it is set to `true`, then a pseudo-TTY will be attached to the service container.

### `jobs.<job_id>.services.<service_name>.volumes`

Set the volumes for the container to use, as if provided with the `--volume` argument of the `docker run` command.

> [!note] Note
> The option is restricted to a list of volumes configured in the runner executing the task.
> See the [Forgejo Runner installation guide](../../../admin/actions/runner-installation/#configuration) for more information.

### `jobs.<job_id>.steps`

An array of steps executed sequentially on the host specified by `runs-on`.

### `jobs.<job_id>.steps.if`

The steps are run if the **expression** evaluates to true.

For instance:

```yaml
jobs:
  my-job:
    steps:
      - name: some-step
        # This step runs when the event that triggerend the workflow was the opening of a pull request
        if: ${{ forgejo.event_name == 'pull_request' && forgejo.event.action == 'opened' }}
      - name: another-step
        # This step runs when the event that triggered the workflow was the closing of a pull request
        if: ${{ forgejo.event_name == 'pull_request' && forgejo.event.action == 'closed' }}
```

### `jobs.<job_id>.defaults`

Set the defaults for this job. These override the [global defaults](#defaults) of the workflow.

### `jobs.<job_id>.defaults.run.working-directory`

Set the default working directory to use for each step.

```yaml
jobs:
  test:
    runs-on: docker
    defaults:
      run:
        working-directory: ./app/
    steps:
      - run: echo SUCCESS
```

### `jobs.<job_id>.defaults.run.shell`

Set the default shell to use for each step.

```yaml
jobs:
  test:
    runs-on: docker
    defaults:
      run:
        shell: sh
    steps:
      - run: echo SUCCESS
```

See [`jobs.<job_id>.steps[*].shell`](#jobsjob_idstepsshell) for more information on the shell values and their semantics.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-shell/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].run`

A shell script to run in the environment specified with
`jobs.<job_id>.runs-on`. It runs as root using the default shell unless
specified otherwise with `jobs.<job_id>.steps[*].shell`. For instance:

```yaml
jobs:
  test:
    runs-on: docker
    container:
      image: alpine:latest
    steps:
      - run: |
          grep Alpine /etc/os-release
          echo SUCCESS
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-container/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].working-directory`

The working directory from which the script specified with `jobs.<job_id>.step[*].run` will run. For instance:

```yaml
- run: test $(pwd) = /tmp
  working-directory: /tmp
```

### `jobs.<job_id>.steps[*].shell`

The interpreter used to run the script specified with `jobs.<job_id>.step[*].run`. If the interpreter is not specified, it defaults to `bash` or `sh` if `bash` is not available.

> [!note] Note
> The fallback to `sh` if `bash` is not available requires a version of
> [Forgejo Runner >= v8.0.0](https://code.forgejo.org/forgejo/runner/src/branch/main/RELEASE-NOTES.md#8-0-0).

The value is a command line where the literal string `{0}` is replaced with the path to a (temporary) file, containing the content of `jobs.<job_id>.steps[*].run`. For example `dash -e {0}` would become `dash -e /tmp/xxx`.

```yaml
jobs:
  test:
    runs-on: docker
    container:
      image: debian:bookworm
    steps:
      - shell: dash -e {0}
        run: echo using dash here
```

Some commonly used interpreters have abbreviated aliases that are expanded into command lines as follows:

- `bash` => `bash --noprofile --norc -e -o pipefail {0}`
- `sh` => `sh -e {0}`
- `node` => `node {0}`
- `python` => `python {0}`

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-shell/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].id`

A unique identifier for the step. You will only need this if you want to refer to inputs or outputs from this step, for example using the `steps.<step_id>.outputs` context.

### `jobs.<job_id>.steps[*].if`

The step is run if the **expression** evaluates to true.

Check out the workflows in [example-if](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-if/) and [example-if-fail](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-if-fail/).

### `jobs.<job_id>.steps[*].uses`

Specifies the repository from which the `Action` will be cloned or a directory where it can be found.

- **Remote actions**
  A relative `Action` such as `uses: actions/checkout@v7` will clone the repository at the URL composed by prepending the `DEFAULT_ACTIONS_URL` (`https://data.forgejo.org` by default, see note below). It is the equivalent of providing the fully qualified URL `uses: https://data.forgejo.org/actions/checkout@v7`. In other words the following:

  ```yaml
  on: [push]
  jobs:
    test:
      runs-on: docker
      steps:
        - uses: actions/checkout@v7
  ```

  is the same as:

  ```yaml
  on: [push]
  jobs:
    test:
      runs-on: docker
      steps:
        - uses: https://data.forgejo.org/actions/checkout@v7
  ```

  > [!note] Note
  > When possible **it is strongly recommended to choose fully qualified URLs** to avoid ambiguities.
  > During installation, the instance administrator may change the `DEFAULT_ACTIONS_URL`.
  > This can cause your workflow to break if the actions you want to use are not available on the specified instance.

  The string after the `@` specifies the version of the action. There are various ways of specifying a version:

  ```yaml
  steps:
    # Using the commit SHA of a specific commit.
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
    # Using the full name of a tag.
    - uses: actions/checkout@v6.0.2
    # Using part of the name of a tag to match the newest version of that tag.
    - uses: actions/checkout@v7
    # Using the name of a branch
    - uses: actions/checkout@main
  ```

  Because it is possible to [compromise tags](https://nvd.nist.gov/vuln/detail/cve-2025-30066), it is recommended to use commit SHAs for security reasons.

- **Remote container actions**
  An OCI container can be used as an action. You may specify a container action with `uses: docker://[host]/[container]:[tag]`.

  For example, the YAML

  ```yaml
  uses: docker://code.forgejo.org/actions/some-action:latest
  ```

  would get the `latest` version of the `some-action` container by user `actions` from `code.forgejo.org`.

- **Local actions**
  An action that begins with a `./` will be loaded from a directory
  instead of being cloned from a repository. The structure of the
  directory is otherwise the same as if it was located in a remote
  repository.

  > [!note] Note
  > The most common mistake when using an action included in the repository under test is to forget
  > to checkout the repository with `uses: actions/checkout@v7`.

  [Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-local-action/).

### `jobs.<job_id>.steps[*].with`

A dictionary mapping the inputs of the action to concrete values. The `action.yml` defines and documents the inputs.

```yaml
on: [push]
jobs:
  ls:
    runs-on: docker
    steps:
      - uses: ./.forgejo/local-action
        with:
          input-two: 'two'
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-local-action/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].with.args`

For actions that are implemented with a `Dockerfile`, the `args` key is used to override the `CMD` passed to the `ENTRYPOINT` of the container. If not specified the `CMD` from the `Dockerfile` will be used.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-docker-action/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].with.entrypoint`

For actions that are implemented with a `Dockerfile`, the `entrypoint` key is used to overrides the `ENTRYPOINT` in the Dockerfile. It must be the path to the executable file to run.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-docker-action/.forgejo/workflows/test.yml)

### `jobs.<job_id>.steps[*].env`

Set environment variables like it's [top-level variant `env`](#env), but only for the current step.

### `jobs.<job_id>.steps[*].continue-on-error`

It set to `true` on a step, the step won't cause the job to fail if it fails. Following steps will still run, and if they succeed the job will be marked successful.

```yaml
steps:
  - run: echo "failing step" && false
    continue-on-error: true
  - run: echo "this step runs and job is successful"
```

When a step with `continue-on-error` fails, its `steps.<step_id>.outcome` will be `failure`, but its `steps.<step_id>.conclusion` will be `success`.

### `jobs.<job_id>.steps[*].timeout-minutes`

Set the number of minutes this step can be performed before it is canceled.

```yaml
steps:
  - run: sleep 301
    timeout-minutes: 5
  - run: echo "this will not get run"
```

## Contexts

A context is an object that contains information relevant to a `workflow` run. For instance the `secrets` context contains the secrets defined in the repository. Each of the following context is defined as a top-level variable when evaluating expressions. For instance `${{ secrets.MYSECRET }}` will be replaced by the value of `MYSECRET`.

| Context name     | Description                                       |
| ---------------- | ------------------------------------------------- |
| secrets          | secrets available in the repository               |
| vars             | variables available in the repository             |
| env              | environment variables defined in the workflow     |
| forgejo or forge | information about the workflow being run          |
| matrix           | information about the current row of the matrix   |
| steps            | information about the steps that have been run    |
| needs            | information about the jobs that have been run     |
| inputs           | the input parameters given to an action           |
| runner           | information about the runner and operating system |

To help with reusing actions and workflows originally developed for GitHub Actions, the `github` context is defined to be the same as the `forgejo` context.

### Availability

Some contexts like `secrets` can only be accessed in certain places. Likewise, functions can be restricted to certain workflow keys. The following table lists which contexts and functions are available where.

| Workflow key                                       | Accessible contexts                                                                                    | Accessible functions                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| `run-name`                                         | `forgejo`, `inputs`, `vars`                                                                            | -                                                        |
| `concurrency`                                      | `forgejo`, `inputs`, `vars`                                                                            | -                                                        |
| `env`                                              | `env`, `forgejo`, `inputs`, `secrets`, `vars`                                                          | -                                                        |
| `jobs.<job-id>.concurrency`                        | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.container`                          | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.container.credentials`              | `env`, `forgejo`, `inputs`, `secrets`, `vars`                                                          | -                                                        |
| `jobs.<job-id>.container.env.<env-id>`             | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `strategy`, `vars`          | -                                                        |
| `jobs.<job-id>.container.image`                    | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.container.options`                  | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.container.volumes.[*]`              | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.continue-on-error`                  | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.defaults.run`                       | `env`, `forgejo`, `inputs `, `matrix`, `needs`, `strategy`, `vars`                                     | -                                                        |
| `jobs.<job-id>.env`                                | `forgejo`, `inputs`, `matrix`, `needs`, `secrets`, `strategy`, `vars`                                  | -                                                        |
| `jobs.<job-id>.environment`                        | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.environment.url`                    | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `steps`, `strategy`, `vars`            | -                                                        |
| `jobs.<job-id>.if`                                 | `env`, `forgejo`, `inputs`, `needs`, `secrets`, `vars`                                                 | `always`, `cancelled`, `failure`, `success`              |
| `jobs.<job-id>.name`                               | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.outputs.<output-id>`                | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | -                                                        |
| `jobs.<job-id>.runs-on`                            | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.secrets.<secret_id>`                | `forgejo`, `inputs`, `matrix`, `needs`, `secrets`, `strategy`, `vars`                                  | -                                                        |
| `jobs.<job-id>.services`                           | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>`              | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>.cmd.[*]`      | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>.credentials`  | `env`, `forgejo`, `inputs`, `secrets`, `vars`                                                          | -                                                        |
| `jobs.<job-id>.services.<service-id>.env.<env-id>` | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `strategy`, `vars`          | -                                                        |
| `jobs.<job-id>.services.<service-id>.image`        | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>.options`      | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>.ports.[*]`    | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.services.<service-id>.volumes.[*]`  | `env`, `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                      | -                                                        |
| `jobs.<job-id>.steps[*].continue-on-error`         | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].env`                       | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].if`                        | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `always`, `cancelled`, `failure`, `success`, `hashFiles` |
| `jobs.<job-id>.steps[*].name`                      | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].run`                       | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].timeout-minutes`           | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].uses`                      | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].with`                      | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.steps[*].working-directory`         | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.strategy`                           | `forgejo`, `inputs`, `needs`, `vars`                                                                   | -                                                        |
| `jobs.<job-id>.strategy.fail-fast`                 | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.strategy.max-parallel`              | `env`, `forgejo`, `inputs`, `job`, `matrix`, `needs`, `runner`, `secrets`, `steps`, `strategy`, `vars` | `hashFiles`                                              |
| `jobs.<job-id>.timeout-minutes`                    | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `jobs.<job-id>.with.<with-id>`                     | `forgejo`, `inputs`, `matrix`, `needs`, `strategy`, `vars`                                             | -                                                        |
| `on.workflow_call.inputs.<input-id>.default`       | `forgejo`, `vars` [^1]                                                                                 | -                                                        |
| `on.workflow_call.outputs.<output-id>.value`       | `forgejo`, `inputs`, `jobs`, `vars`                                                                    | -                                                        |

When you are manipulating a particular context like `env`, you cannot reference context variables defined within the same workflow key. Therefore, the following example will **not** work:

```yaml
on:
  push:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Invalid context reference
        run: |
          echo "$IS_PUSH"
        env:
          EVENT: '${{ forgejo.event_name}}'
          IS_PUSH: "${{ env.EVENT == 'push' }}" # false because env.EVENT is undefined
```

[^1]: While GitHub Actions permits accessing `inputs` in `on.workflow_call.inputs.<input-id>.default`, [it is always empty](https://codeberg.org/forgejo/forgejo/issues/9768#issuecomment-8852121). Therefore, Forgejo Runner does not make it available.

### secrets

A map of the repository secrets. It is empty if the `event` that triggered the `workflow` is `pull_request` and the head is from a fork of the repository.

Example: `${{ secrets.MYSECRETS }}`

### vars

A map of the repository variables.

Example: `${{ vars.MYVARIABLE }}`

### env

A map of the environment variables defined in the workflow.

Example: `${{ env.SOMETHING }}`

In addition, the variables listed in the following table are injected into the environment of the shell executing a particular step. However, they are not included in `env` and can therefore not be referenced when defining a workflow.

| Name                      | Description                                                                                                                                                      |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CI                        | Always set to true.                                                                                                                                              |
| FORGEJO_ACTION            | The numerical id of the current step.                                                                                                                            |
| FORGEJO_ACTION_PATH       | When evaluated while running a `composite` action (i.e. `using: "composite"`), the path where an action files are located.                                       |
| FORGEJO_ACTION_REPOSITORY | For a step executing an action, this is the owner and repository name of the action (e.g. `actions/checkout`).                                                   |
| FORGEJO_ACTIONS           | Always set to true when the Forgejo Runner is running the workflow.                                                                                              |
| FORGEJO_ACTOR             | The name of the user that triggered the `workflow`.                                                                                                              |
| FORGEJO_API_URL           | The API endpoint of the Forgejo instance running the workflow (e.g. https://code.forgejo.org/api/v1).                                                            |
| FORGEJO_BASE_REF          | The name of the base branch of the pull request (e.g. main). Only defined when a workflow runs because of a `pull_request` or `pull_request_target` event.       |
| FORGEJO_ENV               | The path on the runner to the file that sets variables from workflow commands. This file is unique to the current step and changes for each step in a job.       |
| FORGEJO_EVENT_NAME        | The name of the event that triggered the workflow (e.g. `push`).                                                                                                 |
| FORGEJO_EVENT_PATH        | The path to the file on the Forgejo Runner that contains the full event webhook payload.                                                                         |
| FORGEJO_HEAD_REF          | The name of the head branch of the pull request (e.g. my-feature). Only defined when a workflow runs because of a `pull_request` or `pull_request_target` event. |
| FORGEJO_JOB               | The `job_id` of the current job.                                                                                                                                 |
| FORGEJO_OUTPUT            | The path on the runner to the file that sets the current step's outputs. This file is unique to the current step.                                                |
| FORGEJO_PATH              | The path on the runner to the file that sets the PATH environment variable. This file is unique to the current step.                                             |
| FORGEJO_REF               | The fully formed git reference (i.e. starting with `refs/`) associated with the event that triggered the workflow, if any (e.g. `refs/heads/main`).              |
| FORGEJO_REF_NAME          | The short git reference name of the branch or tag associated with the workflow, if any (e.g. `main`).                                                            |
| FORGEJO_REPOSITORY        | The owner and repository name (e.g. forgejo/docs).                                                                                                               |
| FORGEJO_REPOSITORY_OWNER  | The repository owner's name (e.g. forgejo)                                                                                                                       |
| FORGEJO_RUN_ATTEMPT       | Attempt number for this run, beginning at 1 and incrementing when the job is re-run.                                                                             |
| FORGEJO_RUN_NUMBER        | A unique id for the current workflow run in the repository of the workflow.                                                                                      |
| FORGEJO_RUN_ID            | A unique id for the current workflow run in the Forgejo instance.                                                                                                |
| FORGEJO_SERVER_URL        | The URL of the Forgejo instance running the workflow (e.g. https://code.forgejo.org)                                                                             |
| FORGEJO_SHA               | The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow.                                       |
| FORGEJO_TOKEN             | The unique authentication token automatically created for duration of the workflow.                                                                              |
| FORGEJO_WORKSPACE         | The default working directory on the runner for steps, and the default location of the repository when using the checkout action.                                |
| FORGEJO_WORKFLOW_REF      | Ref path to the workflow, for example, `owner/example-respository/.forgejo/workflows/test-workflow.yaml@refs/heads/main`                                         |

To help with reusing actions and workflows originally developed for GitHub Actions, each `FORGEJO_*` variable is also available as a `GITHUB_*` variable with the same suffix (e.g. `GITHUB_REPOSITORY` is the same as `FORGEJO_REPOSITORY`).

> [!note] Note
> The `FORGEJO_*` variables require [Forgejo Runner v7.0.0](https://code.forgejo.org/forgejo/runner/src/branch/main/RELEASE-NOTES.md#7-0-0)
> or above. With earlier versions only the `GITHUB_*` variables were defined.

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-context/.forgejo/workflows/test.yml).

### github

To help with reusing actions and workflows originally developed for GitHub Actions, the `github` context is defined to be the same as the `forgejo` context.

### forgejo

The following are identical to the matching environment variable
(e.g. `forgejo.base_ref` is the same as `env.FORGEJO_BASE_REF`):

> [!note] Note
> The `forge` context is the same as the `forgejo` context.

| Name              | Description                                                                                                                                                      |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action            | The numerical id of the current step.                                                                                                                            |
| action_path       | When evaluated while running a `composite` action (i.e. `using: "composite"`, the path where an action files are located.                                        |
| action_repository | For a step executing an action, this is the owner and repository name of the action (e.g. `actions/checkout`).                                                   |
| actor             | The name of the user that triggered the `workflow`.                                                                                                              |
| api_url           | The API endpoint of the Forgejo instance running the workflow (e.g. https://code.forgejo.org/api/v1).                                                            |
| base_ref          | The name of the base branch of the pull request (e.g. main). Only defined when a workflow runs because of a `pull_request` or `pull_request_target` event.       |
| event             | JSON object containing event details of how the workflow was triggered; details vary depending on the event.                                                     |
| event_name        | The name of the event that triggered the workflow (e.g. `push`).                                                                                                 |
| event_path        | The path to the file on the Forgejo Runner that contains the full event webhook payload.                                                                         |
| head_ref          | The name of the head branch of the pull request (e.g. my-feature). Only defined when a workflow runs because of a `pull_request` or `pull_request_target` event. |
| job               | The `job_id` of the current job.                                                                                                                                 |
| output            | The path on the runner to the file that sets the current step's outputs. This file is unique to the current step.                                                |
| path              | The path on the runner to the file that sets the PATH environment variable. This file is unique to the current step.                                             |
| ref               | The fully formed git reference (i.e. starting with `refs/`) associated with the event that triggered the workflow, if any (e.g. `refs/heads/main`).              |
| ref_name          | The short git reference name of the branch or tag associated with the workflow, if any (e.g. `main`).                                                            |
| repository        | The owner and repository name (e.g. forgejo/docs).                                                                                                               |
| repository_owner  | The repository owner's name (e.g. forgejo)                                                                                                                       |
| run_id            | A unique id for the current workflow run in the Forgejo instance.                                                                                                |
| run_number        | A unique id for the current workflow run in the repository of the workflow.                                                                                      |
| run_attempt       | Attempt number for this run, beginning at 1 and incrementing when the job is re-run.                                                                             |
| server_url        | The URL of the Forgejo instance running the workflow (e.g. https://code.forgejo.org)                                                                             |
| sha               | The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow.                                       |
| token             | The unique authentication token automatically created for duration of the workflow.                                                                              |
| workspace         | The default working directory on the runner for steps, and the default location of the repository when using the checkout action.                                |
| workflow_ref      | Ref path to the workflow, for example, `owner/example-respository/.forgejo/workflows/test-workflow.yaml@refs/heads/main`                                         |

Example: `${{ forgejo.SHA }}`

In addition, the `forgejo` context contains the `forgejo.event` object
which is set to the payload associated with the event
(`forgejo.event_name`) that triggered the workflow.

To find out what this payload is made of, insert a job at the
beginning of the workflow to display the entire context in the web
UI. For instance:

```yaml
debug:
  runs-on: docker
  steps:
    - name: event
      run: |
        cat <<'EOF'
        ${{ toJSON(forgejo) }}
        EOF
```

will show something similar to the following when a label is updated in a pull request:

```json
{
  "event": {
    "action": "label_updated",
    "commit_id": "",
    "label": {
      "color": "795548",
      "description": "Issue or pull request related to testing",
      "exclusive": false,
      "id": 23,
      "is_archived": false,
      "name": "Kind/Testing",
      "url": "https://code.forgejo.org/api/v1/repos/forgejo/runner/labels/23"
    },
    "number": 948,
    "pull_request": {
...
  "workflow": "cascade",
  "run_attempt": "1",
  "run_id": "117195",
  "run_number": "9384",
  "actor": "earl-warren",
  "repository": "forgejo/runner",
  "event_name": "pull_request_target",
  "sha": "09adcc47d250567412c2fc2083ed7dcf61a7b953",
  "ref": "refs/heads/main",
  "ref_name": "main",
  "ref_type": "branch",
  "head_ref": "wip-cascade",
  "base_ref": "main",
  "token": "***",
  "workspace": "/workspace/forgejo/runner",
...
```

> [!note] Note
> Although the automatic token is masked (see `***` in the example above), it is not advisable to display this on a publicly readable instance.

### matrix

An object that exists in the context of a job where `jobs.<job_id>.strategy.matrix` is defined . For instance:

```yaml
jobs:
  actions:
    runs-on: self-hosted
    strategy:
      matrix:
        info:
          - version: v1.22
            branch: next
```

Example: `${{ matrix.info.version }}`

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/commit/b6591e2f71196b12f6e0851774f0bd6e2148ec18/.forgejo/workflows/actions.yml#L22-L37).

### steps

The `steps` context contains information about the `steps` in the current job that have
an id specified (`jobs.<job_id>.step[*].id`) and have already run.

The `steps.<step_id>.outputs` object is a key/value map of the output of the
corresponding step, defined by writing to `$FORGEJO_OUTPUT`. For instance:

```yaml
- id: mystep
  run: echo 'check=good' >> $FORGEJO_OUTPUT
- run: test ${{ steps.mystep.outputs.check }} = good
```

Values that contain newlines can be set as follows:

```yaml
- id: mystep
  run: |
    cat >> $FORGEJO_OUTPUT <<EOF
    thekey<<STRING
    value line 1
    value line 2
    STRING
    EOF
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-expression/.forgejo/workflows/test.yml).

### needs

The `needs` context contains information about the `jobs` in the current workflow that have
already run before the current job (i.e. that need it in the sense of [`jobs.<job_id>.needs`](#jobsjob_idneeds)).

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-needs/.forgejo/workflows/test.yml).

### inputs

The `inputs` context maps keys (strings) to values (also strings) when running an action. They are provided as `jobs.<job_id>.step[*].with`
in a step where `jobs.<job_id>.step[*].uses` specifies an action.

For example, an action defined with the following `action.yaml`:

```yaml
inputs:
  message-input:
    description: 'The message to print'

runs:
  using: 'composite'
  steps:
    - run: echo ${{ inputs.message-input }}
```

would have an input `message-input` which could be used by a `workflow.yaml` like this:

```yaml
steps:
  - uses: my-action
    with:
      message-input: 'message to print'
```

[Check out the example](https://code.forgejo.org/forgejo/end-to-end/src/branch/main/actions/example-local-action/.forgejo/workflows/test.yml)

### runner

The `runner` context contains information about the runner that is executing the current job.

Example: `${{ runner.os }}`

The context variables are also available as environment variables in each step. However, they are not included in `env` (see [env context](#env-1)).

| Name                | Environment Variable | Description                                                                                                                                                                                                                                                                                                                |
| ------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `runner.os`         | `RUNNER_OS`          | The operating system of the runner; either `Linux`, `Windows`, `macOS`, or `undefined`.                                                                                                                                                                                                                                    |
| `runner.arch`       | `RUNNER_ARCH`        | The CPU architecture of the runner; either `X86`, `X64`, `ARM`, `ARM64`, or `undefined`.                                                                                                                                                                                                                                   |
| `runner.temp`       | `RUNNER_TEMP`        | This is a temporary directory provided by the runner.                                                                                                                                                                                                                                                                      |
| `runner.tool_cache` | `RUNNER_TOOL_CACHE`  | This is a directory where tools can be installed. Defaults to `/opt/hostedtoolcache`. Despite the name, this directory is not cached across builds anymore but the variable is kept for compatibility reasons. If you want to enable the toolcache for actions that supports it, mount a volume to `/opt/hostedtoolcache`. |

For compatibility with GitHub Actions, `runner.os` and `runner.arch` only contain values defined by GitHub Actions. On platforms not supported by GitHub Actions, they contain `undefined`. If Microsoft ever expands GitHub Actions' platform support, Forgejo Runner will replace `undefined` with the value used by GitHub Actions.

## Environment variables

Forgejo defines some environment variables that are available on each step. Please see the [env context](#env-1) and the [runner context](#runner) for a list of the environment variables.

# Forgejo Actions | Security OpenID Connect

Forgejo Actions allows for workflows to request OpenID Connect compatible JWT ID tokens from Forgejo to exchange with external systems for credentials once a trust relationship between the external service and Forgejo is established.

These trust relationships can be configured in platforms such as AWS, GCP, and Azure, to make assertions on metadata in Forgejo ID tokens and exchange them for granularly scoped and short-lived access tokens.

### Benefits

- Eliminates the needs for secrets stored in Forgejo for compatible external systems
- Access tokens generated by external systems are short-lived and expire automatically
- Allows for granular control over authentication and authorization in external systems via assertions on metadata in the Forgejo ID tokens

### High level flow

![diagram](../../_mermaid/_images/user/actions/open-id-connect-flow-1.svg)

1. A workflow run makes a request to Forgejo to generate an OIDC (OpenID Connect) ID token.
2. Forgejo generates and returns an OIDC ID token that contains metadata about the run, repo, actor, and other information.
3. The returned OIDC ID token can be exchanged with external platforms for short-lived credentials in these systems.
4. The external platform will make a request to Forgejo for required metadata and keys to validate the received token.
5. Forgejo responds to these requests with OIDC metadata, including where to find signing keys.
6. The external platform uses this metadata, the keys, and a configured trust relationship to validate access and provide short-lived credentials if these validations pass.
7. The workflow run receives these short-lived credentials, and can now use them to access the external platform.

### Prerequisites

A trust relationship must be established in a compatible external system to allow for exchanging an ID token for short-lived credentials.

Setup instructions for some compatible external systems are as follows:

- [AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html)
- [GCP](https://docs.cloud.google.com/iam/docs/workload-identity-federation)
- [Azure](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation)
- [Tailscale](https://tailscale.com/kb/1581/workload-identity-federation)

> [!note] Note
> Improperly configured trust relationships can allow for unintended access to third party systems.
> The `sub`, `iss`, and `aud` claims should be validated in the trust relationship at the minimum.
> Trust relationships should be configured to be as strict and narrowly scoped as is possible for your usecase.

### Token structure

JWT tokens are comprised of three main components:

1. A header which contains information about the signing algorithm and key used for signing the token
2. A body or payload which contains
3. A signature of the other two sections based on the signing algorithm used

These sections are Base64 URL encoded and joined by `.` to form the JWT.
Examples of what the decoded sections look like follow below.

##### Header

```json
{
  "alg": "RS256",
  "kid": "iSrBtRq6EaN6605cSatQEnafFF82rOfm_22_d_V6ehQ",
  "typ": "JWT"
}
```

##### Body

```json
{
  "actor": "user1",
  "aud": "exampleAudience",
  "event_name": "push",
  "exp": 1767217099,
  "iat": 1767213499,
  "iss": "https://example.com/api/actions",
  "nbf": 1767213499,
  "ref": "refs/heads/master",
  "ref_protected": "false",
  "ref_type": "branch",
  "repository": "user1/testing",
  "repository_owner": "user1",
  "run_attempt": "1",
  "run_id": "43",
  "run_number": "43",
  "sha": "76cb2978acb72029ac23277a6192eea1707c6a2c",
  "sub": "repo:user1/testing:ref:refs/heads/master",
  "workflow": "test.yml",
  "workflow_ref": "user1/testing/.forgejo/workflows/test.yml@refs/heads/master"
}
```

#### Signature

The format of the signature will depend on the algorithm used for signing the JWT ID token.

### Reference

#### Requesting an ID token

OpenID connect must be enabled in workflow files at either the [workflow](../reference/#enable-openid-connect) or [individual job](../reference/#jobsjob_idenable-openid-connect) level to be able to generate ID tokens from action runs.

When enabled, `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL` will be injected into the runners environment. A token can then be generated by running:

```shell
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=exampleCustomAudience"
```

where the `audience` is an optional query param can be customized to match the value of the `aud` claim expected by the trust relationship in the external platform.

When unspecified, the `aud` value will default to the Forgejo instance's URL with the repository owner as a suffix.

For example, `https://example.com/user1`.

#### Subject format

When the event that triggered the workflow is a `pull_request` the subject format is:

```shell
repo:[repository]:pull_request
```

For all other events that trigger workflows, the subject format is:

```shell
repo:[repository]:ref:[ref]
```

where `[ref]` is the fully formed git reference (i.e. starting with `refs/`) associated with the event that triggered the workflow, if any (e.g. `refs/heads/main`)

`[repository]` may take two different forms:

- For repositories which had Forgejo Actions enabled prior to Forgejo 16, it is the owner and repository name (e.g., forgejo/docs).
- For repositories which had Forgejo Actions enabled after Forgejo 16, it is the owner and owner's numeric ID, followed by the repository and repository's numeric ID (e.g., forgejo-123/docs-456). [^1]

Repositories can be converted to the `[repository]` format that includes the numeric ID by disabling Forgejo Actions in the repository settings, and then re-enabling Forgejo Actions. This conversion is permanent and cannot be undone.

#### Standard claims

The Forgejo ID token contains a number of well-known standard (also known as registered) claims as defined by [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1).

| Claim | Summary    | Description                                                                                                                                                                        |
| ----- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aud` | Audience   | The intended audience of the ID token. This can be configured using the `audience` URL param when requesting a token, and defaults to `[forgejo instance URL]/[repository owner]`. |
| `iss` | Issuer     | The issuer of the ID token. This will be in the format: `[forgejo instance URL]/api/actions`. For example, `https://example.com/api/actions`.                                      |
| `sub` | Subject    | The subject claim for the token. See [subject format](#subject-format) for more details.                                                                                           |
| `iat` | Issued at  | The time that the token was issued.                                                                                                                                                |
| `exp` | Expires at | The expiry time of the token.                                                                                                                                                      |
| `nbf` | Not before | The time after which this token is valid.                                                                                                                                          |

#### Custom claims

The Forgejo ID token contains a number of custom claims that contain Forgejo specific information.

| Claim                 | Summary                | Description                                                                                                                                                |
| --------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actor`               | Triggering actor       | The name of the user that triggered the `workflow`.                                                                                                        |
| `actor_id`            | Triggering actor       | The numeric ID of the user that triggered the `workflow`. [^1]                                                                                             |
| `base_ref`            | Base reference         | The name of the base branch of the pull request (e.g. main). Only defined when a workflow runs because of a `pull_request` or `pull_request_target` event. |
| `event_name`          | Event name             | The name of the event that triggered the workflow (e.g. `push`).                                                                                           |
| `ref`                 | Reference              | The fully formed git reference (i.e. starting with `refs/`) associated with the event that triggered the workflow, if any (e.g. `refs/heads/main`).        |
| `ref_protected`       | Is reference protected | `true` if branch protections are configured for the ref that triggered the workflow run.                                                                   |
| `ref_type`            | Reference type         | The type of the `ref` (`branch` or `tag` for example).                                                                                                     |
| `repository`          | Repository             | The owner and repository name (e.g. forgejo/docs).                                                                                                         |
| `repository_id`       | Repository             | The numeric ID of the repository. [^1]                                                                                                                     |
| `repository_owner`    | Repository owner       | The repository owner's name (e.g. forgejo)                                                                                                                 |
| `repository_owner_id` | Repository owner       | The numeric OD of the repository owner. [^1]                                                                                                               |
| `run_attempt`         | Run attempt            | Attempt number for this run, beginning at 1 and incrementing when the job is re-run.                                                                       |
| `run_id`              | Run identifier         | A unique id for the current workflow run in the Forgejo instance.                                                                                          |
| `run_number`          | Run number             | A unique id for the current workflow run in the repository of the workflow.                                                                                |
| `sha`                 | Git SHA                | The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow.                                 |
| `workflow`            | Workflow name          | The name of the workflow that triggered the run.                                                                                                           |
| `workflow_ref`        | Workflow ref           | Ref path to the workflow, for example, `owner/example-respository/.forgejo/workflows/test-workflow.yaml@refs/heads/main`                                   |

[^1]: The `actor_id`, `repository_id`, and `repository_owner_id` claims are immutable identifiers for the users and repository that they reference. If a user or repository are renamed, their ID will stay the same when the related name claim will change. If a user or repository are deleted, even if a new user or repository claims their old name, the ID will change.

# Forgejo Actions | Security of Pull Requests

A pull request may contain code that changes what is run by Forgejo
Actions, for instance if it modifies a file in the
`.forgejo/workflows` directory. When such a pull request originates
from a fork of a public repository on a Forgejo instance with open
registration, the author may be a malicious user. For this reason the
workflows of such pull requests need to be approved before they are run.

Users with elevated permissions on a repository can approve or deny
workflows runs using the pull request conversation page.

### Trust management

Trust is either granted or denied to the author of a pull request.

![trust management area](../../_images/user/actions/trust-management-area.webp)

When using the `Approve once` button, all pending workflows will be
allowed to run. However future workflows will need another manual
approval (for instance if a new commit is pushed to the same pull request).

Using the `Approve always` button will trust the author of the pull request to always
run workflows on this repository.

It is possible to revoke that trust at any time.

![revoke trust](../../_images/user/actions/trust-revoke.webp)

The page displaying the run of each blocked workflow shows a link to
the trust management area of the pull request.

![run pending approval link](../../_images/user/actions/trust-run-pending-link.webp)

### Which users need approval?

The users who only have read access to the repository need approval
before workflows triggered by a pull request they authored can be run.

The users who have elevated permissions on the repository do not need
approval, even if they author a pull request from a forked
repository. That includes:

- Collaborators with write access
- Team members with write permissions on the `Actions` unit
- `Owners` of the repository
- Instance admins

The author of a pull request is held responsible for all commits they push to
their pull request, including commits written by a third party. For
instance, if a pull request is from a branch of a repository to which
multiple users are allowed to push they are also implicitly trusted if
the author of the pull request is trusted.

### What pull requests need approval?

Assuming the author of a pull request is not implicitly trusted
because of their elevated permissions, approval will be required for
pull requests that are either:

- from a fork of the repository
- created using the `AGit` workflow

### Which user can grant approval?

Trust management is available to users who are either:

- Collaborators with write access
- Team members with write permissions on the `Actions` unit
- `Owners` of the repository
- Instance admins

### Which workflow files are used?

When a trusted user submits a pull request, workflows found in the
pull request content are used (except for the ones using the
`pull_request_target` event), taking into account any changes done to
these files as part of that pull request.

If the pull request is not from a trusted user and a workflow is
triggered by a trusted user, the workflows found in the target branch of the
pull request will be used instead of those found in the pull request.

A concrete use case is to allow the repository owner to set a label on
a pull request from an untrusted user without having to verify if the
pull request contains a malicious workflow.

### Trust expiration

When a permanently trusted user has not submitted a pull request for
over three months, the trust they may have been granted on a given
repository will be automatically revoked.

### Blocked users

When a [user is blocked](../../blocking-user), the trust they were
granted on all repositories owned by the user doing the blocking will
be revoked.

All workflow runs created on their behalf on those repositories will
be canceled.

# Forgejo Actions | Security

Forgejo Actions is a very flexible CI/CD system. It lets you write workflows that execute arbitrary code, with contents of potentially private repositories. This brings some security risks. This page details the security design of the Actions system, and any considerations you should be aware of.

## Security Design

Forgejo Actions consists of two main parts: The Forgejo server, which contains the repository and hands out workflow runs; and the Forgejo Runner, which receives workflow runs and executes them. One server may have many different runner attached, which are registered to different users or organizations.

### Runner selection

When a workflow is triggered, the Forgejo instance will try to find a runner to run the workflow. The runner will be selected from a pool of runners that consists of the following groups:

- Runners registered to the Forgejo instance by the instance administrators
- Runners registered to the user account or organization account that the repository belongs to
- Runners registered to the specific repository

Your workflow will never be executed by a runner that does not belong to one of these groups.

### Pull requests from forks

When a repository is publicly available on a Forgejo instance with open registration, any user can open a pull request that includes code to be executed by Forgejo Actions. Checkout the [section dedicated to trust management](../security-pull-request) for more information.

### Automatic token

When a workflow is started, a unique authentication token is automatically created. It can be used to read the repositories associated with the workflow, even when they are private. This token is automatically destroyed when the workflow finishes.

This token can only be used for interactions with the repository it was created for, and any attempt to use it on other repositories will return a 404 error.

### Pull requests

Workflows triggered by the `pull_request` event are an exception. The token generated for these runs does _not_ have write permissions for the repository, since a pull request is not verified and could contain an untested or malicious workflow.

#### pull_request_target

As an alternative to `pull_request` there is `pull_request_target`. Workflows with this event will run in the context of the `base` of the Pull Request, instead of the incoming commits. This means that the changes proposed in the PR are _not_ available in the workflow. You cannot use this event to perform workflows that would check the incoming changes.

Because no untrusted code is available to the runner in this context, the restrictions on the token are lifted. That means you can use this event type to write to the repository, for example to change labels or place comments.

### Runner

The Forgejo Runner attempts to isolate different workflow runs from each other as much as possible. How this is achieved depends on the kind of runner it is.

#### Docker, Podman and LXC runners

In the case of a runner which uses OCI (Docker, Podman) or LXC containers, a new container will be created for each workflows. The containers provide isolation from the host system and other containers.

However, if the container environment is misconfigured it may be possible for a workflow to escape its confinement and perform potentially malicious actions. This may compromise the runner, which means that any future workflows sent to this runner may also be compromised.

#### Host runners

Host runners have no real isolation. When a workflow finishes the appropriate folders are cleaned up, so any workflows that stay within the intended bounds shouldn't have any effect on each other.

It is _trivial_ to 'escape' on a host runner, and all sorts of malicious things can be done.

### Secrets

Secrets can be used to store passwords or tokens for use in a workflow. They are stored encrypted in the Forgejo database, and sent out to workflows when they are needed. [More information](../basic-concepts/#secrets).

Forgejo Runner attempts to automatically redact secrets from the logs.

## Security considerations

When using Actions, keep in mind the following considerations:

### Runner

- Container runners are generally safe, but they may be compromised if misconfigured.
- Never trust a host runner if it is shared with other users.
- The safest option is to [host your own runner](../../../admin/actions/runner-installation/), and use it only for your trusted repositories.

### Secrets

- Use the [secrets feature](../basic-concepts/#secrets) to store API tokens and passwords for use in workflows.
- Do not print secrets to output, as these may be publicly visible.

# Forgejo Actions | Troubleshooting

When a workflow or an action fails to run as expected, the following steps may help to figure out where to look for errors.

## Schema validation

As of [Forgejo Runner v8.0.0 and above](https://code.forgejo.org/forgejo/runner/releases/tag/v9.0.0) both workflows and actions are validated against a schema and will fail to run if they do not pass. Errors will be displayed:

- When browsing a workflow from the web interface (available in [Forgejo v13.0.0](https://codeberg.org/forgejo/forgejo/milestone/21377) and above).
  ![Viewing a Forgejo Actions workflow with schema validation errors](../../_images/user/actions/validate-edit-workflow.webp)
- In the list of actions, when hovering on the warning sign next to a workflow that fails schema validation (available in [Forgejo v13.0.0](https://codeberg.org/forgejo/forgejo/milestone/21377) and above).
  ![Action list with schema validation errors on hover](../../_images/user/actions/validate-action-list.webp)
- In the logs of a workflow run, in the `Set up job` step for an action used by the workflow (available in all Forgejo versions).
  ![Set up job logs](../../_images/user/actions/validate-setup-failure.webp)
- In the logs of a workflow run, in the `Complete job` step for an action used by an action (two levels of indirection) used by a workflow (available in all Forgejo versions).
  ![Complete job logs with schema validation errors](../../_images/user/actions/validate-complete-job.webp)

It is also possible to verify that the workflows and/or actions found in a repository successfully pass schema validation [using the Forgejo Runner CLI](https://code.forgejo.org/forgejo/runner/releases/tag/v9.0.0) (e.g. `forgejo-runner validate --repository https://example.com/my/repo`) (available in the [Forgejo Runner v9.0.0](https://code.forgejo.org/forgejo/runner/releases/tag/v9.0.0) and above).
A `pre-commit` hook is also provided to help automate this, see [Advanced features](../advanced-features/#pre-commit-hook) for more details.

## Forgejo Runner logs

If the logs of the action displayed in the web UI are incomplete, it is worth looking into the [logs of the Forgejo Runner itself](../../../admin/actions/configuration/#configuration-file-reference).

## Common Workflow Issues

Keep in mind the following caveats in case you have problems with your workflow:

### Non-Standard Git Credentials in the workflow

Usually, [`actions/checkout`](https://code.forgejo.org/actions/checkout) creates git credentials for your workflow "on the fly" and inserts them into the git config (Example: `http.https://codeberg.org/.extraheader=AUTHORIZATION: basic ***`).

For Standard use cases, this is what you'll probably want. However, if you need to interact with **different repositories** in your workflow (e.g. your private repositories, usually with renovate or similar tools), you may want to use different credentials like an API key with specific scope.

It might be possible that your custom git credentials are set correctly, but the "auto-generated" key from the git config is still preferred, leading to failures when interacting with other repositories.

To fix this, set `persist-credentials` to `false` in your checkout action's configuration:

```yaml
- uses: https://code.forgejo.org/actions/checkout@v7
  with:
    persist-credentials: false
```

# Authorized Integrations

Authorized Integrations provide a capability for API requests to Forgejo to be authenticated based upon a JSON Web Token (JWT) generated and signed by an external system. Examples of compatible systems include: [Forgejo Actions](../../actions/security-openid-connect/) (on the same, or a different, Forgejo instance), [GitHub Actions](https://docs.github.com/en/actions/concepts/security/openid-connect), [GitLab CI/CD](https://docs.gitlab.com/ci/secrets/id_token_authentication/), and [Amazon Web Services](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html). Any system that meets Forgejo's [technical requirements](#technical-details) is compatible.

Authorized Integrations do not use any static long-lived credentials, which makes them easier to manage and secure than Access Tokens. Instead, JWTs are created that can have a short expiry, and their validity can be checked against signing keys that can be rotated without service discontinuity. Authorized Integrations are preferred over using Access Tokens whenever possible in a supported system.

Users can configure Authorized Integrations by visiting their `Settings` page, and selecting the `Authorized Integrations` menu item. From there, a user can create a new Authorized Integration, edit an existing Authorized Integration, or delete Authorized Integrations.

Creating a new Authorized Integration requires selecting the type of system to integrate with. Integrating with [Forgejo Actions (Local)](#forgejo-actions-local) provides a simple user interface for allowing a workflow running on this Forgejo instance to access resources. Any other system that meets Forgejo's [technical requirements](#technical-details) for generating JWTs can access Forgejo using the [Generic JWT](#generic-jwt) option.

All Authorized Integrations have three sections: identifying information for the Authorized Integration, validating information for what access is permitted from the third-party system, and what [capabilities are granted](#capabilities-permitted) to the Authorized Integration.

![Screenshot of the identifying information section of an Authorized Integration](../../_images/user/authorized_integrations/forgejo_actions_local_top.webp)

In the identifying information section pictured above, three fields are provided:

- A **Name** used for the Authorized Integration list
- A **Description** which can be used to take notes on the Authorized Integration
- An **Audience** which is a unique identifier for the Authorized Integration, and must be used in the external system when generating a JWT. This is not a confidential, secret value.
  - **Audience** is not visible until an Authorized Integration is saved for the first time.
  - This value is the `aud` JWT claim, which is generated by Forgejo, and must be included by the external system in the JWT to identify it.

## Forgejo Actions (Local)

A Forgejo Actions (Local) Authorized Integration can be used when a Forgejo Action needs to access resources on the same Forgejo instance. Forgejo Actions provides an [automatic `${{ forgejo.token }}`](../../actions/basic-concepts/#automatic-token); however, this token has limited capabilities. Authorized Integrations allow Forgejo Actions to extend their reach into operations that are not permitted by the automatic token.

To use this capability, first create an Authorized Integration. In the "Forgejo Actions (Local)" section, restrictions are provided:

![Screenshot of the Forgejo Actions (Local) section of an Authorized Integration](../../_images/user/authorized_integrations/forgejo_actions_local_middle.webp)

- **Source Repository**: Only workflows executing in this repository will be able to use this Authorized Integration. A repository must be selected.
  - If the repository is transferred to a new owner, the Authorized Integration will need to be edited and the repository selected again.
- **Workflow file (without directory)**: A file name, such as `testing.yml`, which matches the workflow that you want to permit using this Authorized Integration.
  - Must **not** include the path of the file.
  - Can contain wildcards, such as `test-*.yml`.
  - Can be empty to allow any workflow in the repository.
- **Git reference**: Git reference where authorized Actions would be running, such as `refs/heads/main`.
  - Can contain wildcards, such as `refs/tags/v1.*`.
  - Can be empty to allow any reference.
- **Event**: Only enable the Authorized Integration when the workflow was triggered by one of the selected events. For example, an Authorized Integration with the `schedule` event would only be enabled if a workflow was run `on: schedule: {...}`.
  - Can be empty to allow for any event.

Once an Authorized Integration is created, these steps are taken to use it:

1. Set `enable-openid-connect: true` in the Workflow file to enable the generation of a JWT.
1. Make an HTTP call to Forgejo to generate a JWT, providing the `audience` value of your Authorized Integration.
1. Interact with Forgejo's APIs, package registry, and Git operations with the JWT using HTTP Bearer authorization (HTTP header `Authorization: Bearer [...jwt...]`).

The example Forgejo Actions workflow below is annotated with these three steps:

```yaml
on:
  issues:
    types: [labeled]

# Step 1: Enable JWT generation:
enable-openid-connect: true

jobs:
  label-repeater:
    runs-on: docker
    steps:
      - name: fetch jwt
        id: jwt
        env:
          # Step 2: Use the "Audience" value; in this example it
          # is separated out from the small script below,
          # but it can be placed inline as well.
          # Safe to commit to public code -- not confidential.
          AUD: u:1:f92855c4-d9b2-40e2-a136-432b16bb7a78
        # Step 2: HTTP request to Forgejo. This script uses `curl`
        # and `jq` and assumes they are available, but any HTTP
        # client can be used.
        run: |
          jwt=$( \
            curl --fail \
            -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$AUD" \
            | jq -r ".value" \
          )
          # ::add-mask:: tells Forgejo Runner that the JWT
          # is a secret which should be masked in the logs.
          echo "::add-mask::$jwt"
          echo "jwt=$jwt" >> $FORGEJO_OUTPUT
      # Step 3: Example here adds a new comment to an issue when it
      # is labeled.  `curl` and `jq` are used again to make a simple
      # HTTP request with bearer token authorization.
      - env:
          LABEL_NAME: ${{ forgejo.event.label.name }}
        run: |
          body=$(jq -n --arg label "$LABEL_NAME" '{body: "Label added: \($label)"}')
          curl \
            -v --fail \
            -H "Authorization: bearer ${{ steps.jwt.outputs.jwt }}" \
            -H "Content-Type: application/json" \
            -X POST \
            -d "$body" \
            "${{ forgejo.server_url }}/api/v1/repos/${{ forgejo.event.repository.full_name }}/issues/${{ forgejo.event.issue.number }}/comments"
```

A JWT generated by `$ACTIONS_ID_TOKEN_REQUEST_URL` has a fixed validity defined by the Forgejo server configuration value [`[actions].ID_TOKEN_EXPIRATION_TIME`](../admin/config-cheat-sheet/#actions-actions), which defaults to 1 hour. If a workflow is expected to run for an extended period of time, it is recommended that a new JWT is fetched at a regular interval which will have a renewed expiry time.

Retrieving a JWT from Forgejo will always be possible, as long as `enable-openid-connect: true` is specified in the workflow. If the JWT's claims do not match the Authorized Integration's configuration -- for example, if it is generated from the wrong repository, workflow file, Git reference, or event -- operations that require authenticating to Forgejo will fail even if the JWT is successfully generated. In the example above, the `fetch jwt` step will always succeed, but the API interaction to Forgejo may fail.

## Generic JWT

A Generic JWT Authorized Integration can be used with any external system that meets Forgejo's [technical details](#technical-details) for generating a JWT. It provides direct access for a user to provide an issuer URL and a set of rules on how to validate a generated JWT.

The external system is responsible for generating a JWT and accessing Forgejo's API. Forgejo will validate the JWT was authentically generated by the external system by validating its signature, and compare its claims against the flexible claim rules in the Authorized Integration, and then permit access to the API.

![Screenshot of the Generic JWT Rules section of an Authorized Integration](../../_images/user/authorized_integrations/forgejo_generic_middle.webp)

**Issuer** is the URL of the service that will be issuing a JWT.

- Must be an `https` URL, which supports a subpath `.well-known/openid-configuration` JSON document ([technical details](#technical-details)).
- Example: `https://codeberg.org/api/actions` would be used if Forgejo Actions, running on Codeberg, was being used to generate a JWT to access this Forgejo system.
- Forgejo supports an internal URI `urn:forgejo:authorized-integrations:actions`, which is treated the same as `https://<this-instance>/api/actions` for the current Forgejo host. [^1]

[^1]: If the [Forgejo Actions (Local)](#forgejo-actions-local) options were not sufficient for a user, they could use the Generic JWT capability to write specific claim rules and use the `urn:forgejo:authorized-integrations:actions` issuer. For example, an additional claim rule could be defined on [`actor`](../../actions/security-openid-connect/#custom-claims) to permit only a single user's workflows to access an Authorized Integration; this isn't supported by Forgejo Actions (Local) due to its rarity, but can be implemented with a Generic JWT. The internal issuer name automatically refers to the current host, which allows it to: bypasses the HTTPS requirement for JWT validation, and continue to function if Forgejo's hostname changes.

**Claim Rules JSON** is a JSON document with specific **Claim Rules** that will be validated against the JWT.

A JWT can have standardized claims that are defined in [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519), such as the JWT's expiration time (`exp`). Forgejo will automatically enforce logical restrictions on these standard JWT claims. However, with claims like a JWT's **Subject** (`sub`), or custom claims that are provided by the external system, Forgejo relies on the Authorized Integration's Claim Rules to define the required values for those claims.

At the root of the JSON document is an object with `"rules"`, which is an array of rules. Each rule is a logical comparison between a `"claim"` and a value. The following values for `"compare"` operators are supported.

> [!note] Note
> Single-value operators use `"value"` (singular) and list operators use `"values"` (plural).

- `"eq"`
  - Compares the value of the `"claim"` against the provided `"value"`. Must be exactly equal.
  - Example:
    ```json
    {
      "claim": "sub",
      "compare": "eq",
      "value": "repo:some-owner/some-repo:pull_request"
    }
    ```
- `"in"`
  - Compares the value of the `"claim"` against any of the values in the list `"values"`. Must be exactly equal to one of the values.
  - Example:
    ```json
    {
      "claim": "sub",
      "compare": "in",
      "values": ["repo:some-owner/some-repo:pull_request", "repo:some-owner/some-repo:ref:refs/heads/main"]
    }
    ```
- `"glob"`
  - Compares the value of the `"claim"` against the provided `"value"`, with [wildcard expansion](https://pkg.go.dev/github.com/gobwas/glob#Compile). The `"claim"` must be a string, and the entire value must be matched by the `"value"`.
  - Example:
    ```json
    {
      "claim": "sub",
      "compare": "glob",
      "value": "repo:some-owner/*:pull_request"
    }
    ```
- `"glob-in"`
  - Compares the value of the `"claim"` against the provided `"values"`, with [wildcard expansion](https://pkg.go.dev/github.com/gobwas/glob#Compile). The `"claim"` must be a string, and the entire value must be matched by one of the `"values"`.
  - Example:
    ```json
    {
      "claim": "sub",
      "compare": "glob-in",
      "values": ["repo:some-owner/*:pull_request", "repo:other-owner/*:pull_request"]
    }
    ```
- `"nest"`
  - If the value of the `"claim"` is an object, the fields within the object must match the rules provided by `"nested"`. Multiple rules may be provided within the `"nested"` value, and all rules must match positively for the claim rule to be validated.
  - Example:
    ```json
    {
      "claim": "https://sts.amazonaws.com/",
      "compare": "nest",
      "nested": {
        "rules": [
          {
            "claim": "aws_account",
            "compare": "eq",
            "value": "123456789012"
          }
        ]
      }
    }
    ```
    This rule would ensure that the `"aws_account"` value was `"123456789012"` in a nested JWT claim value such as this:
    ```json
    {
      "aud": "u:1:f92855c4-d9b2-40e2-a136-432b16bb7a78",
      "sub": "arn:aws:iam::123456789012:role/MyRoleName",
      "iss": "https://sts.amazonaws.com/",
      // ... other claims ...
      "https://sts.amazonaws.com/": {
        "aws_account": "123456789012",
        "principal_id": "AROAXXXXXXXXXXXXXXXXX:session-name"
        // ... other claims ...
      }
    }
    ```

### Accessing a Remote Forgejo Instance in a Workflow

> [!note] Note
> An Authorized Integration with [Forgejo Actions (Local)](#forgejo-actions-local) is easier to use and configure and meets most people's needs. This section is only relevant for remote access, or as an example for how a Generic JWT claim rule would work.

Forgejo Actions and Authorized Integrations can be configured so that a workflow running on one Forgejo can access resources on another Forgejo. As an example of a Generic JWT source, this would allow a workflow running on Codeberg to access a self-hosted Forgejo (`forgejo.example.org`):

1. On the self-hosted `forgejo.example.org` Forgejo instance, create an Authorized Integration with the "Generic JWT" option.
1. Enter a name and description.
1. In the "Issuer (`iss` Claim)" field, enter the Forgejo instance's URL, plus `/api/actions`. In this case, `https://codeberg.org/api/actions`.
1. Add claim rules by referencing [Forgejo Action's OIDC Custom Claims](../../actions/security-openid-connect/#custom-claims). For example:

   ```json
   {
     "rules": [
       {
         "claim": "repository_owner",
         "compare": "eq",
         "value": "some-owner"
       },
       {
         "claim": "repository_name",
         "compare": "eq",
         "value": "some-repo"
       },
       {
         "claim": "ref",
         "compare": "glob-in",
         "values": ["refs/tags/v*.*", "refs/heads/main"]
       }
     ]
   }
   ```

1. Define the [Capabilities](#capabilities-permitted) to be granted by selecting permissions and resources.
1. Save the Authorized Integration, and copy the "Audience" field that is generated.
1. On Codeberg where the workflow will run, create a Forgejo Action that generates an OIDC token and makes API calls. [Forgejo Actions (Local)](#forgejo-actions-local) has an example workflow with JWT generation.
   - Insert the "Audience" field from the previous step into the workflow. It is not confidential, and can be committed to the repository publicly.

## Capabilities Permitted

All Authorized Integrations have the same "Capabilities Permitted" section. This determines what permissions the Authorized Integration has, and which resources those permissions can be applied to. The permissions available are the same as those for access tokens, and are documented in the [Access Token Scope](../authentication/token-scope/) page.

![Screenshot of the "Capabilities Permitted" section of the UI, showing a list of permissions](../../_images/user/authorized_integrations/forgejo_actions_local_bottom.webp)

## Technical Details

An external system that uses Forgejo's Authorized Integrations must meet these system requirements:

1. It must generate signed JWTs consistent with [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519), at a minimum containing these claims:
   - `iss` claim: must be an `https://`-scheme URL representing the system that generated the JWT. Example: `https://codeberg.org/api/actions` is the issuer for JWTs issued by Forgejo Actions running on Codeberg.
   - `aud` claim: must contain a value generated by Forgejo when a new Authorized Integration is created.
1. It must host authorization server metadata consistent with [RFC 8414 Section 2](https://datatracker.ietf.org/doc/html/rfc8414#section-2) at `.well-known/openid-configuration` under the issuer URL, which contains an `issuer` field, and a required `jwks_uri` field.
   - Example: [https://codeberg.org/api/actions/.well-known/openid-configuration](https://codeberg.org/api/actions/.well-known/openid-configuration)
1. The authorization server metadata's `jwks_uri` must be an accessible `https` URL on the same host as the issuer which accesses an [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) JSON Web Key Set which contains the public key used to sign JWTs generated by this issuer.
   - Example: [https://codeberg.org/api/actions/.well-known/keys](https://codeberg.org/api/actions/.well-known/keys)

When these requirements are met, an external system can access Forgejo with a JWT and Forgejo will perform this validation process:

1. Provide the JWT to Forgejo in the `Authorization` header.
   - As an HTTP Bearer Token, consistent with [RFC 6750 section 2.1](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1): `Authorization: Bearer eyJhb...`
   - In the Authorization header in the format `Authorization: Token eyJhb...`, an informal convention for token-based authentication that Forgejo supports.
1. Forgejo will search for an Authorized Integration with the matching issuer (`iss` claim) and audience (`aud` claim). If none are found, then the request will not be authenticated.
1. Forgejo will check the JWT against any configured claim rules in its Authorized Integration.
1. Forgejo will fetch the `.well-known/openid-configuration` metadata from the issuer.
1. Forgejo will fetch the `jwks_uri` URL from the metadata.
1. Forgejo will validate that the JWT is signed by an authorized signing key.
1. The request will be authenticated as the user who owns the Authorized Integration, and authorized to perform actions within the permissions defined on the Authorized Integration.

# API

Forgejo's API is documented via Swagger/OpenAPI v2, the raw JSON file can be accessed via https://next.forgejo.org/swagger.v1.json.
A human readable UI for the swagger file can be accessed via https://next.forgejo.org/api/swagger.

Using the API is furthermore documented in [API Usage](./usage/).

# API Usage

The Forgejo API for all versions that have the same major number
(e.g. the major number of Forgejo 7.0.0 is 7) are compatible. There
are breaking changes (e.g. removal of an API endpoint) when the major
number changes and the release notes of this major version provide
explanations to help developers upgrade their software
accordingly. Read more about the [Forgejo numbering scheme](../versions/).

> [!warning] Warning
> The following instructions use `forgejo.example.org` as placeholder for the Forgejo instance URL and other placeholders,
> such as `{yourusername}` and `{yourpassword}`, which need to be replaced before running these commands.

## Enabling/configuring API access

By default, the API is enabled on Forgejo instances, but instance administrators may choose to [disable it](../../admin/config-cheat-sheet/#api-api).

## Authentication

Forgejo supports these methods of API authentication:

- HTTP basic authentication
- `Authorization: Bearer ...` header in HTTP headers
- `Authorization: token ...` header in HTTP headers

### Generating and listing API tokens

A new token can be generated with a `POST` request to
`/users/:name/tokens`.

Note that `/users/:name/tokens` is a special endpoint and requires you
to authenticate using `BasicAuth` and a password, as follows:

```sh
$ curl -H "Content-Type: application/json" -d '{"name":"test","scopes":["write:package"]}' -u {yourusername}:{yourpassword} https://forgejo.example.org/api/v1/users/{username}/tokens
{"id":1,"name":"test","sha1":"9fcb1158165773dd010fca5f0cf7174316c3e37d","token_last_eight":"16c3e37d","scopes":["write:package"]}
```

The keys of the JSON data object are `name` (the display name for the token) and `scopes` (list of [Access Token scope](../../authentication/token-scope/) names).

The `sha1` (the token) is only returned once and is not stored in
plain-text. It will not be displayed when listing tokens with a `GET`
request; e.g.

```sh
$ curl --url https://{yourusername}:{yourpassword}@forgejo.example.org/api/v1/users/{username}/tokens
[{"name":"test","sha1":"","token_last_eight":"........"},{"name":"dev","sha1":"","token_last_eight":"........"}]
```

To use the API with basic authentication with two factor authentication (2FA)
enabled, you'll need to send an additional header that contains the one-time password (6-digit rotating token).
An example of the header is `X-Forgejo-OTP: 123456` where `123456`
is where you'd place the code from your authenticator.
Here is how the request would look like in curl:

```sh
$ curl -H "X-Forgejo-OTP: 123456" --url https://{yourusername}:{yourpassword}@forgejo.example.org/api/v1/users/{username}/tokens
```

You can also create an API key token via your Forgejo installation's web
interface: `Settings | Applications | Generate New Token`.

### OAuth2 Provider

Access tokens obtained from Forgejo's OAuth2 provider are accepted by these methods:

- `Authorization: Bearer ...` header in HTTP headers
- `token=...` parameter in URL query string
- `access_token=...` parameter in URL query string

#### More on the `Authorization:` header

For historical reasons, Forgejo needs the word `token` included before
the API key token in an authorization header, like this:

```
Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675
```

In a `curl` command, for instance, this would look like:

```sh
curl "https://forgejo.example.org/api/v1/repos/test1/test1/issues" \
    -H "accept: application/json" \
    -H "Authorization: token 65eaa9c8ef52460d22a93307fe0aee76289dc675" \
    -H "Content-Type: application/json" -d "{ \"body\": \"testing\", \"title\": \"test 20\"}" -i
```

As mentioned above, the token used is the same one you would use in
the `token=` string in a GET request.

### Sudo

The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo.

## Pagination

The API supports pagination. The `page` and `limit` parameters are used to specify the page number and the number of items per page. As well, the `Link` header is returned with the next, previous, and last page links if there are more than one page. The `x-total-count` is also returned to indicate the total number of items.

```sh
curl -v "https://forgejo.example.org/api/v1/repos/search?limit=1"
...
< link: <https://forgejo.example.org/api/v1/repos/search?limit=1&page=2>; rel="next",<https://forgejo.example.org/api/v1/repos/search?limit=1&page=5252>; rel="last"
...
< x-total-count: 5252
```

The default and maximum values for the `page` parameter can be obtained from the `https://forgejo.example.org/api/v1/settings/api` endpoint.

```sh
$ curl https://forgejo.example.org/api/v1/settings/api
{
  "max_response_items": 50,
  "default_paging_num": 30,
  "default_git_trees_per_page": 1000,
  "default_max_blob_size": 10485760
}
```

## API Guide

API Reference guide is auto-generated by Swagger and available on:
`https://forgejo.example.org/api/swagger`.
For instance, find the reference guide of [Forgejo Next](../forgejo-next/) at <https://try.next.forgejo.org/api/swagger>.

The OpenAPI document is at:
`https://forgejo.example.org/swagger.v1.json`

## SDKs

Find [clients using the API](https://delightful.coding.social/delightful-forgejo/#clients)
and [API client libraries](https://delightful.coding.social/delightful-forgejo/#api-client-libraries)
at the list of [Delightful Forgejo](https://delightful.coding.social/delightful-forgejo).

# Forgejo numbering scheme

The Forgejo versions look like `X.Y.Z+gitea-A.B.C`
(e.g., 7.0.0+gitea-1.22.0). The Forgejo version is the first part,
before the '+' (e.g., 7.0.0), and the following metadata, after the '+',
shows the Gitea version with which the API is compatible
(e.g., 1.22.0).

## Obtaining the Forgejo version

The Forgejo version is displayed in the footer of the web UI, in the
output of the `forgejo --version` CLI, and returned by two API
endpoints:

- [example.com/api/v1/version](https://code.forgejo.org/api/swagger/#/miscellaneous/getVersion)
- [example.com/api/forgejo/v1/version](https://code.forgejo.org/api/forgejo/swagger/#/default/getVersion)

For Forgejo 7.0.0 and above, the two endpoints return the same value
and they match what is displayed by the CLI or the web UI.

## Compatibility with Gitea

As of Forgejo 7.0.0, tools designed to work with Gitea 1.22.0 and
below are compatible and do not need any modification to keep working.

In the future, if a tool wants to assert the level of compatibility of
a Forgejo version with Gitea, it can:

- Obtain the version number [example.com/api/v1/version](https://code.forgejo.org/api/swagger/#/miscellaneous/getVersion) using the same API endpoint as Gitea
- If the returned version contains '+gitea-', keep what follows as the compatible Gitea version number

For instance, Gitnex will not notice a difference when the version
number is bumped from Forgejo 1.21.7-0 to Forgejo 7.0.0. It only uses
version numbers to verify if a new feature is available in a newer
version. The last time such a check was added was with Gitea 1.17,
which was published in July 2022.

## Semantic Version

[SemVer](https://semver.org/spec/v2.0.0.html) allows users to understand the scope of a software update at first glance, based on the following:

- `<MAJOR>` is increased for breaking changes.
- `<MINOR>` is increased for backwards-compatible new features.
- `<PATCH>` is increased for backwards-compatible bug fixes.

Forgejo changes reflected in the version could be:

- a command, an option, or an argument, for a CLI
- a route path, a query parameter, or a body property, for a REST API
- a text node, a button, or a field, for a GUI

Since Forgejo has all of the above, changes to all of those components are taken into consideration when creating a new version number.

### Stable versions

The structure of the version number is `<MAJOR>.<MINOR>.<PATCH>+gitea-<GITEA VERSION>`, where:

- `<MAJOR>.<MINOR>.<PATCH>` is conformant to [Semantic Versioning 2.0.0](https://semver.org/#semantic-versioning-200)
- `gitea-<GITEA VERSION>` is the Gitea version this Forgejo release is compatible with

### Experimental and pre-release versions

The structure of the version number is `<MAJOR>.<MINOR>.<PATCH>-dev-<SHA>+gitea-<GITEA VERSION>`, where:

- `<MAJOR>.<MINOR>.<PATCH>` is conformant to [Semantic Versioning 2.0.0](https://semver.org/#semantic-versioning-200)
- `<SHA>` is the Git SHA from which the release was built
- `gitea-<GITEA VERSION>` is the Gitea version this Forgejo release is compatible with

## Legacy forgejo numbering scheme

In Forgejo versions prior to 7.0.0, i.e., from 1.18.0-1 to 1.21.7-0
included, the Forgejo version matched the Gitea version by removing
the string following the trailing dash (-) and the release cycles were
synchronized.

The version number A.B.C-N was displayed with the dash (-) replaced by a plus
(+) (e.g., Forgejo 1.18.0-1 was displayed as 1.18.0+1). The two API
endpoints returned different results:

- [example.com/api/v1/version](https://code.forgejo.org/api/swagger/#/miscellaneous/getVersion) returned `{"version":"A.B.C+N"}`
- [example.com/api/forgejo/v1/version](https://code.forgejo.org/api/forgejo/swagger/#/default/getVersion) returned `{"version":"X.Y.Z+N-gitea-A.B.C"}`

The Forgejo semantic number `X.Y.Z` could only be found in the second
endpoint and was used to prepare for switching to semantic
versioning. It was advertised in release notes and [documented in the
user reference guide](https://forgejo.org/docs/v1.21/user/semver/).

# OAuth2 provider

Forgejo supports acting as an OAuth2 provider to allow third party applications to access its resources with the user's consent.

> [!note] Note
> Scoped tokens or personal access tokens are entirely different from OAuth2;
> see the [Access Token scope](../token-scope/) section for more information.

Forgejo can act as an instance-wide OAuth2 provider. To achieve that, OAuth2 applications must be created on the `/admin/applications` page.

> [!note] Note
> Third-party applications obtaining a token for a user via such an application will have administrative rights.
> OAuth2 scopes are not yet implemented.

## Endpoints

| Endpoint                 | URL                                 |
| ------------------------ | ----------------------------------- |
| OpenID Connect Discovery | `/.well-known/openid-configuration` |
| Authorization Endpoint   | `/login/oauth/authorize`            |
| Access Token Endpoint    | `/login/oauth/access_token`         |
| OpenID Connect UserInfo  | `/login/oauth/userinfo`             |
| JSON Web Key Set         | `/login/oauth/keys`                 |

## Supported OAuth2 Grants

At the moment, Forgejo only supports the [**Authorization Code Grant**](https://tools.ietf.org/html/rfc6749#section-1.3.1) standard with additional support for the following extensions:

- [Proof Key for Code Exchange (PKCE)](https://tools.ietf.org/html/rfc7636)
- [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)

To use the Authorization Code Grant as a third-party application, it is required to register a new application via the "Settings" (`/user/settings/applications`) section. To test or debug, you can use the web tool https://oauthdebugger.com/.

## Client types

Forgejo supports both confidential and public client types, [as defined by RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1).

For public clients, a redirect URI of a loopback IP address such as `http://127.0.0.1/` allows any port. Avoid using `localhost`, [as recommended by RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252#section-8.3).

## Git authentication

OAuth2 can be used as an alternative to a public SSH key or basic authentication (user/password) to obtain the required
read or write access permissions. It relies on Git [credential helpers](https://git-scm.com/docs/gitcredentials#_custom_helpers)
such as:

- [Git Credential Manager](https://github.com/git-ecosystem/git-credential-manager)
- [git-credential-oauth](https://github.com/hickford/git-credential-oauth)

They are both [pre-configured server
side](../../admin/advanced/oauth2-provider/) but need to be installed and
configured client side.
The following example uses [git-credential-oauth](https://github.com/hickford/git-credential-oauth) on a Debian GNU/Linux machine
to authenticate on https://code.forgejo.org:

- [download the binary tarball](https://github.com/hickford/git-credential-oauth/releases/download/v0.11.0/git-credential-oauth_0.11.0_linux_amd64.tar.gz)
- extract the binary in `/usr/local/bin/git-credential-oauth`
- verify it is found with `git credential-oauth`
- add the following to `~/.gitconfig` (note that `a4792ccc-144e-407e-86c9-5e7d8d9c3269` is a hardcoded value that is identical for all Forgejo instances)
  ```ini
  [credential]
    helper = cache --timeout 7200
    helper = oauth
  [credential "https://code.forgejo.org"]
    oauthClientId = a4792ccc-144e-407e-86c9-5e7d8d9c3269
    oauthAuthURL = /login/oauth/authorize
    oauthTokenURL = /login/oauth/access_token
  ```
- `git clone https://code.forgejo.org/earl-warren/test`
- `git push` will open a new page on the default browser, looking like this:
  ![git-credential-oauth OAuth2 grant page](../../_images/user/oauth2-provider/oauth-git-credential-client.png)
- subsequent `git push` will reuse the token obtained from OAuth2 as long as it remains in the [git credential-cache](https://git-scm.com/docs/git-credential-cache) (i.e., 2h / 7200s)

> [!note] Note
> Scopes are not implemented for OAuth2 tokens and they can be used to
> execute any actions on behalf of the user, not just Git-related actions.
> Scoped application tokens or SSH keys limited to interactions with the
> repository should be preferred in environments where security is a concern.

It is possible for any user to manually register a new OAuth2 application on the `/user/settings/applications` page for the purpose of using Git [credential helpers](https://git-scm.com/docs/gitcredentials#_custom_helpers) different from the pre-registered ones. In that case, the `~/.gitconfig` setting (`oauthClientId`, etc.) needs to be adapted accordingly.

## Examples

### Using Codeberg as an authentication source

In this example, `https://forgejo.example.com` will be configured to add the option to delegate user registration to https://codeberg.org.

![Login page with Codeberg authentication source](../../_images/user/oauth2-provider/authsource-intro-login-page.png)

> [!note] Note
> In OAuth2 jargon, `https://forgejo.example.com` is the OAuth2 client and Codeberg is the OAuth2 provider.

- Choose an arbitrary but distinctive name for the OAuth2 provider (e.g., **Codeberg**).
- Choose an existing Codeberg user to create the OAuth2 application. It does not need to be a user with elevated privileges (e.g., **user-for-oauth-application**).
- On https://codeberg.org, log in as **user-for-oauth-application**.
  - Visit https://codeberg.org/user/settings/applications and create a new OAuth2 application. There needs to be only one redirect URI, composed with the arbitrary name that was chosen above: `https://forgejo.example.com/user/oauth2/Codeberg/callback`.
    ![Create a new OAuth2 application](../../_images/user/oauth2-provider/authsource-provider-create.png)
  - When created, the OAuth2 application is given a **Client ID** and a **Client secret** that `https://forgejo.example.com` will need to let https://codeberg.org know it is an authorized OAuth2 client.
    ![Client ID and secret of a new OAuth2 application](../../_images/user/oauth2-provider/authsource-provider-show.png)
- On `https://forgejo.example.com`, log in as a user with admin privileges.
  - Create a new authentication source on `https://forgejo.example.com`, the Forgejo instance that is going to act as the OAuth2 client, allowing its users to register using the account they have on https://codeberg.org.
    - Visit `https://forgejo.example.com/admin/auths/new` to create the authentication source with:
      - **Authentication type:** OAuth2
      - **Authentication name:** the arbitrary name that was chosen above (e.g., **Codeberg**)
      - **OAuth2 provider:** OpenID Connect
      - **Client ID:** copy/pasted from the OAuth2 application created on Codeberg
      - **Client Secret:** copy/pasted from the OAuth2 application created on Codeberg
      - **Icon URL:** https://design.codeberg.org/logo-kit/icon.svg
      - **OpenID Connect Auto Discovery URL:** https://codeberg.org/.well-known/openid-configuration
      - Leave all other fields at their default values.
        ![Create a new OAuth2 authentication source](../../_images/user/oauth2-provider/authsource-client-create.png)
    - It will show in the list of authentication sources at `https://forgejo.example.com/admin/auths`.
      ![List of OAuth2 authentication source](../../_images/user/oauth2-provider/authsource-client-list.png)
- On `https://forgejo.example.com`, not logged in.
  - Visit `https://forgejo.example.com/user/login`.
    ![Login page with Codeberg authentication source](../../_images/user/oauth2-provider/authsource-intro-login-page.png)
  - Click on **Sign in with Codeberg** to be redirected to Codeberg and authorize `https://forgejo.example.com` to obtain the details of your account (user name, email, etc.). If you are not already logged in to Codeberg, you will need to before this authorization request is presented to you.
    ![Authorizing forgejo.example.com](../../_images/user/oauth2-provider/authsource-intro-login-confirm.png)
  - Review the pre-filled information that will be used to create your account on `https://forgejo.example.com`.
    ![Filling account information](../../_images/user/oauth2-provider/authsource-intro-login-create.png)
  - You are redirected to the home page of the newly created account.
    ![User home page](../../_images/user/oauth2-provider/authsource-intro-login-home.png)

### Confidential client

> [!note] Note
> This example does not use PKCE.

1. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:

   ```http
   https://[YOUR-FORGEJO-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE
   ```

   The `CLIENT_ID` can be obtained by registering an application in the settings. The `STATE` is a random string that will be sent back to your application after the user authorizes. The `state` parameter is optional but should be used to prevent CSRF attacks.

   The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the `REDIRECT_URL`, for example:

   ```http
   https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE
   ```

2. Using the provided `code` from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with `application/json` and `application/x-www-form-urlencoded` body, for example:

   ```http
   POST https://[YOUR-FORGEJO-URL]/login/oauth/access_token
   ```

   ```json
   {
     "client_id": "YOUR_CLIENT_ID",
     "client_secret": "YOUR_CLIENT_SECRET",
     "code": "RETURNED_CODE",
     "grant_type": "authorization_code",
     "redirect_uri": "REDIRECT_URI"
   }
   ```

   Response:

   ```json
   {
     "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjowLCJleHAiOjE1NTUxNzk5MTIsImlhdCI6MTU1NTE3NjMxMn0.0-iFsAwBtxuckA0sNZ6QpBQmywVPz129u75vOM7wPJecw5wqGyBkmstfJHAjEOqrAf_V5Z-1QYeCh_Cz4RiKug",
     "token_type": "bearer",
     "expires_in": 3600,
     "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjoxLCJjbnQiOjEsImV4cCI6MTU1NzgwNDMxMiwiaWF0IjoxNTU1MTc2MzEyfQ.S_HZQBy4q9r5SEzNGNIoFClT43HPNDbUdHH-GYNYYdkRfft6XptJBkUQscZsGxOW975Yk6RbgtGvq1nkEcklOw"
   }
   ```

   The `CLIENT_SECRET` is the unique secret code generated for this application. Please note that the secret will only be visible after you created/registered the application with Forgejo and cannot be recovered. If you lose the secret, you must regenerate the secret via the application's settings.

   The `REDIRECT_URI` in the `access_token` request must match the `REDIRECT_URI` in the `authorize` request.

3. Use the `access_token` to make API requests to access the user's resources.

### Public client (PKCE)

PKCE (Proof Key for Code Exchange) is an extension to the OAuth flow which allows for a secure credential exchange without the requirement to provide a client secret.

> [!note] Note
> Please ensure you have registered your OAuth application as a public client.

To achieve this, you have to provide a `code_verifier` for every authorization request. A `code_verifier` has to be a random string with a minimum length of 43 characters and a maximum length of 128 characters. It can contain alphanumeric characters as well as the characters `-`, `.`, `_`, and `~`.

Using this `code_verifier` string, a new one called `code_challenge` is created by using one of two methods:

- If you have the required functionality on your client, set `code_challenge` to be a URL-safe base64-encoded string of the SHA256 hash of `code_verifier`. In that case, your `code_challenge_method` becomes `S256`.
- If you are unable to do so, you can provide your `code_verifier` as a plain string to `code_challenge`. Then you have to set your `code_challenge_method` as `plain`.

After you have generated these values, you can continue with your request.

1. Redirect the user to the authorization endpoint in order to get their consent for accessing the resources:

   ```http
   https://[YOUR-FORGEJO-URL]/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&code_challenge_method=CODE_CHALLENGE_METHOD&code_challenge=CODE_CHALLENGE&state=STATE
   ```

   The `CLIENT_ID` can be obtained by registering an application in the settings. The `STATE` is a random string that will be sent back to your application after the user authorizes. The `state` parameter is optional, but should be used to prevent CSRF attacks.

   The user will now be asked to authorize your application. If they authorize it, the user will be redirected to the `REDIRECT_URL`, for example:

   ```http
   https://[REDIRECT_URI]?code=RETURNED_CODE&state=STATE
   ```

2. Using the provided `code` from the redirect, you can request a new application and refresh token. The access token endpoint accepts POST requests with `application/json` and `application/x-www-form-urlencoded` body, for example:

   ```http
   POST https://[YOUR-FORGEJO-URL]/login/oauth/access_token
   ```

   ```json
   {
     "client_id": "YOUR_CLIENT_ID",
     "code": "RETURNED_CODE",
     "grant_type": "authorization_code",
     "redirect_uri": "REDIRECT_URI",
     "code_verifier": "CODE_VERIFIER"
   }
   ```

   Response:

   ```json
   {
     "access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjowLCJleHAiOjE1NTUxNzk5MTIsImlhdCI6MTU1NTE3NjMxMn0.0-iFsAwBtxuckA0sNZ6QpBQmywVPz129u75vOM7wPJecw5wqGyBkmstfJHAjEOqrAf_V5Z-1QYeCh_Cz4RiKug",
     "token_type": "bearer",
     "expires_in": 3600,
     "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJnbnQiOjIsInR0IjoxLCJjbnQiOjEsImV4cCI6MTU1NzgwNDMxMiwiaWF0IjoxNTU1MTc2MzEyfQ.S_HZQBy4q9r5SEzNGNIoFClT43HPNDbUdHH-GYNYYdkRfft6XptJBkUQscZsGxOW975Yk6RbgtGvq1nkEcklOw"
   }
   ```

# Access Token Scope

Forgejo supports scoped access tokens, which allow users to restrict tokens to operate only on selected URL routes. Scopes are grouped by high-level API routes, and further refined as follows:

- `read`: `GET` routes
- `write`: `POST`, `PUT`, `PATCH`, and `DELETE` routes (in addition to `GET`)

Forgejo token scopes are as follows:

| Name                                      | Description                                                                                                                                                  |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **(no scope)**                            | Not supported. A scope is required even for public repositories.                                                                                             |
| **activitypub**                           | `activitypub` API routes: ActivityPub related operations.                                                                                                    |
| &nbsp;&nbsp;&nbsp; **read:activitypub**   | Grants read access for ActivityPub operations.                                                                                                               |
| &nbsp;&nbsp;&nbsp; **write:activitypub**  | Grants read/write/delete access for ActivityPub operations.                                                                                                  |
| **admin**                                 | `/admin/*` API routes: Site-wide administrative operations (hidden for non-admin accounts).                                                                  |
| &nbsp;&nbsp;&nbsp; **read:admin**         | Grants read access for admin operations, such as getting cron jobs or registered user emails.                                                                |
| &nbsp;&nbsp;&nbsp; **write:admin**        | Grants read/write/delete access for admin operations, such as running cron jobs or updating user accounts.                                                   |
| **issue**                                 | `issues/*`, `labels/*`, `milestones/*` API routes: Issue-related operations.                                                                                 |
| &nbsp;&nbsp;&nbsp; **read:issue**         | Grants read access for issue operations, such as getting issue comments, issue attachments, and milestones.                                                  |
| &nbsp;&nbsp;&nbsp; **write:issue**        | Grants read/write/delete access for issue operations, such as posting or editing an issue comment or attachment, and updating milestones.                    |
| **misc**                                  | Miscellaneous and settings top-level API routes.                                                                                                             |
| &nbsp;&nbsp;&nbsp; **read:misc**          | Grants read access to miscellaneous operations, such as getting label and gitignore templates.                                                               |
| &nbsp;&nbsp;&nbsp; **write:misc**         | Grants read/write/delete access to miscellaneous operations, such as markup utility operations.                                                              |
| **notification**                          | `notification/*` API routes: User notification operations.                                                                                                   |
| &nbsp;&nbsp;&nbsp; **read:notification**  | Grants read access to user notifications, such as which notifications users are subscribed to and reading new notifications.                                 |
| &nbsp;&nbsp;&nbsp; **write:notification** | Grants read/write/delete access to user notifications, such as marking notifications as read.                                                                |
| **organization**                          | `orgs/*` and `teams/*` API routes: Organization and team management operations.                                                                              |
| &nbsp;&nbsp;&nbsp; **read:organization**  | Grants read access to org and team status, such as listing all organizations a user has visibility to, teams, and team members.                              |
| &nbsp;&nbsp;&nbsp; **write:organization** | Grants read/write/delete access to org and team status, such as creating and updating teams and updating org settings.                                       |
| **package**                               | `/packages/*` API routes: Packages operations.                                                                                                               |
| &nbsp;&nbsp;&nbsp; **read:package**       | Grants read access to package operations, such as reading and downloading available packages.                                                                |
| &nbsp;&nbsp;&nbsp; **write:package**      | Grants read/write/delete access to package operations. Currently the same as `read:package`.                                                                 |
| **repository**                            | `/repos/*` API routes except `/repos/issues/*`: Repository file, pull-request, and release operations.                                                       |
| &nbsp;&nbsp;&nbsp; **read:repository**    | Grants read access to repository operations, such as getting repository files, releases, and collaborators.                                                  |
| &nbsp;&nbsp;&nbsp; **write:repository**   | Grants read/write/delete access to repository operations, such as getting and updating repository files, creating pull requests, and updating collaborators. |
| **user**                                  | `/user/*` and `/users/*` API routes: User-related operations.                                                                                                |
| &nbsp;&nbsp;&nbsp; **read:user**          | Grants read access to user operations, such as getting user repository subscriptions and user settings.                                                      |
| &nbsp;&nbsp;&nbsp; **write:user**         | Grants read/write/delete access to user operations, such as updating user repository subscriptions, followed users, and user settings.                       |

## Repository and organization access

When creating a Forgejo access token, three options are available for which repositories and organizations the access tokens can access: [All (public, private, and limited)](#all-public-private-and-limited), [Public only](#public-only), and [Specific repositories](#specific-repositories).

It is recommended to create the most restrictive access token possible that meets a user's functional needs. If an access token is compromised in the future, a more restrictive access token will have a smaller impact on a user's operations.

### All (public, private, and limited)

Access tokens created with the "All (public, private, and limited)" option will have no restrictions placed upon them. Any permissions can be selected.

### Public only

Access tokens created with the "Public only" option will be limited, preventing access to private resources that the user would normally have access to. Any permissions can be selected, but APIs affecting these objects will be limited:

- Repository access will be limited to public repositories.
- Issue access will be limited to issues in public repositories.
- Organization access will be limited to public organizations.
- User access will be limited to public users.
- Notification access will be limited to notifications on public repositories.
- Package access will be limited to packages owned by a public user or organization.

Additional capabilities which are usually [granted to Forgejo site administrators and repository administrators](#administrator-capabilities) are restricted when using a public only token.

### Specific repositories

Access tokens created with the "Specific repositories" option will be limited to the repositories selected.

All public repositories are accessible, but access is limited to read-only APIs if the public repository is not included in the specified repositories.

Only the `read:repository`, `write:repository`, `read:issue`, and `write:issue` permissions can be used for a specific repository access token. Other scopes are not permitted because the API would not have a related repository to check against the selected list.

Specific repository access tokens cannot be used to perform administrative operations within a repository, even if the token owner could usually perform these actions. For example, the token cannot be used to transfer the repository to a new owner, cannot be used to add a collaborator to the repository, cannot change the repository from private to public.

Additional capabilities which are usually [granted to Forgejo site administrators and repository administrators](#administrator-capabilities) are restricted when using a specific repository token.

### Administrator capabilities

Forgejo site administrators and repository administrators have elevated capabilities, unless a public only or specific repository access token is used.

For example, site administrators can perform actions such as, but not limited to:

- impersonate other users by adding `?sudo={username}` to the API endpoints,
- have `write:repository` access to all repositories,
- access `GET /users/{username}/activities/feeds` for any user,
- transfer repository ownership immediately without confirmation,
- bypass quota restrictions,
- and create repositories for other users.

Repository administrators can perform actions such as, but not limited to:

- view tracked time entered by other users, and add tracked time for other users, on an issue in the repository,
- query the permissions of other users against the repository,
- add and remove collaborators,
- and convert a mirror repository into a normal repository.

# Blocking a user

Blocking another user is desirable if they are acting maliciously or are spamming your repository (for instance, by adding unsolicited comments in issues). For such cases, Forgejo provides the functionality to block other users. When you block a user, Forgejo does not explicitly notify them, but they may learn through an interaction with you that is blocked.

## How to block Someone

In order to block another user, go to their profile page and click on the button with three dots. The menu that is shown has a "Block" action, click on it.

![Profile card where block button is shown](../../_images/user/block/profile.webp)

A popup will show; please read carefully what blocking another user implies, and if you accept the implications, click on Yes.

![Popup where implications of the block action is listed](../../_images/user/block/popup.png)

### List of blocked Users and Unblocking

If you would like to see the list of users you've blocked, navigate to your user profile in the top-right corner and click on the settings item.

![Dropdown that shows up after clicking on the profile in the navigation bar](../../_images/user/block/profile_dropdown.png)

Select the Blocked Users item in the menu. This navigates you to the page where the users you've blocked are listed.

![Blocked Users page being selected in the settings menu](../../_images/user/block/user_settings.png)

To unblock someone, click on the unblock button next to their name.

![List of people that are currently blocked by the user](../../_images/user/block/user_list_blocks.png)

## How to block Someone as an organization

It is possible to block a user from an organization; this has the same implications as a normal user blocking another user. To block a user from an organization, you must be on that organization's Owners team.

Navigate to your organization's settings and select the Blocked Users page.

![Blocked Users page being selected in the settings menu](../../_images/user/block/org_page.png)

Find the person you want to block in the search bar, select the user, and click block.

![Search results being shown to the search query in the search bar](../../_images/user/block/org_search.png)

You will now see the blocked users in the list, along with the date they were blocked.

![Blocked person being shown in the blocked users list](../../_images/user/block/org_successful.png)

To unblock that person, you can click the unblock button next to their name.

![Blocked person being shown in the blocked users list](../../_images/user/block/org_unblock.png)

## Implications of blocking a User

When you block a user:

- You stop following them.
- They stop following you.
- They are removed as collaborators on repositories you own as an individual.
- All pending repository transfers caused by them are removed.

After you've blocked them:

- They cannot cause any notifications for you anymore by mentioning you.
- They cannot open issues or pull requests on repositories you own.
- They cannot add reactions to your comments.
- They cannot post comments on issues and pull requests you've opened.
- They cannot transfer repositories to you.

# Labels

You can use labels to classify issues and pull requests and to improve your overview of them.

Labels can be created by going to the `Issues` page of a repository and clicking on `Labels` to show the labels management page.

![repository labels management](../../_images/user/labels/repository-labels-management.png)

For organizations, you can define organization-wide labels that are shared with all organization repositories, including both already-existing repositories as well as newly created ones. Organization-wide labels can be created in the organization `Settings`.

![organization labels management](../../_images/user/labels/organization-labels-management.png)

## Creating Labels

From the labels management page, click on the `New Label` button.

![create a label](../../_images/user/labels/label-new.png)

Labels have a mandatory name, a mandatory color, an optional description, and must either be exclusive or not (see [Scoped Labels](#scoped-labels) below).

When you create a repository (or an organization), you can ensure certain labels exist by using the `Issue Labels` option. This option lists a number of available label sets that are configured globally on your instance.

## Archiving Labels

When a label is no longer useful but needs to be kept around because
it is still associated with existing pull requests or issues, it can be archived.

- The label won't show up as a suggestion when you're adding/editing labels.
- The label cannot be assigned to new issues or pull requests.

To archive a label, edit the label and click the `Archive` checkbox.

![archive a label](../../_images/user/labels/label-archive.png)

## Scoped Labels

Scoped labels are used to ensure at most a single label with the same scope is assigned to an issue or pull request. For example, if labels `kind/bug` and `kind/enhancement` have the Exclusive option set, an issue can only be classified as a bug or an enhancement.

![list of labels](../../_images/user/labels/label-list.png)

A scoped label must contain `/` in its name (not at either end of the name). The scope of a label is determined based on the **last** `/`, so for example the scope of label `scope/subscope/item` is `scope/subscope`.

## Applying Labels

Labels can be applied to issues or pull requests on their pages by clicking on `Labels`. In the dropdown menu, labels can be added or removed. Of a set of scoped labels with the same scope, only one can be selected. Archived labels are disabled for addition.

![apply labels](../../_images/user/labels/label-apply.png)

## Filtering by Label

Issue and pull request lists can be filtered by label. Selecting multiple labels shows issues and pull requests that have all selected labels assigned.

By holding alt and clicking the label, issues and pull requests with the chosen label are excluded from the list.

# Automatically Linked References in Issues, Pull Requests, and Commit Messages

When an issue, pull request, or comment is posted, the text description is parsed
in search of references. These references will be shown as links in the Issue View
and, in some cases, produce certain _actions_.

Likewise, commit messages are parsed when they are listed, and _actions_
can be triggered when they are pushed.

To prevent the creation of unintended references, there are certain rules
for them to be recognized. For example, they should not be included inside code
text. They should also be separated from the surrounding text with spaces.

## User, Team, and Organization Mentions

When text in the form `@username` is found and `username` matches the name
of an existing user, a _mention_ reference is created. This will be shown
by changing the text into a link to said user's profile, and possibly creating
a notification for the mentioned user depending on whether they have
the necessary permission to access the contents.

Example:

> [@John](#), can you give this a look?

This is also valid for teams and organizations:

> [@Documenters](#), we need to plan for this.
> [@CoolCompanyInc](#), this issue concerns us all!

Teams will receive email notifications when appropriate, but whole organizations won't.

Commit messages do not produce user notifications.

## Commits

Commits can be referenced using their SHA1 hash, or a portion of it of
at least seven characters. They will be shown as a link to the corresponding
commit.

Example:

> This bug was introduced in [e59ff077](#)

## Code References

It is possible to reference any portion of a file in a repository by using the permalink feature inside the file viewer:

![Permalinking of a line range](../../_images/user/linked-references/permalink-coderange.png)

If used inside an issue or pull request, this is automatically rendered into an inline preview of the portion of that file.

> [!note] Note
> Alternatively, you can also use the "Reference in a new issue" action to automatically create an issue with the correct permalink.

Example:

> See the code here: https://next.forgejo.org/Mai-Lapyst/test/src/commit/66719555bd242b9ad7c3fb9eef2b388826d87872/hook.go#L191-L200 It sets all of the options for this.

![Rendered result of a permalink code reference](../../_images/user/linked-references/rendered-permalink.png)

There are some restrictions in place:

- Code linked in private repositories will not be displayed if the current viewer has insufficient permissions.
- It can only ever be a permalink; linking to files by a branch or tag will not result in a preview.
- There is a maximum amount of lines that will be rendered. By default, this is 50.
- Only permalinks to the current instance will work.

## Issues and Pull Requests

A reference to another issue or pull request can be created using the simple
notation `#1234`, where _1234_ is the number of an issue or pull request
in the same repository. These references will be shown as links to the
referenced content.

The effect of creating this type of reference is that a _notice_ will be
created in the referenced document, provided the creator of the reference
has reading permissions on it.

Example:

> This seems related to [#1234](#)

Issues and pull requests in other repositories can be referred to as well
using the form `owner/repository#1234`:

> This seems related to [mike/compiler#1234](#)

Alternatively, the `!1234` notation can be used as well. Even when in Forgejo
a pull request is a form of issue, the `#1234` form will always link to
an issue; if the linked entry happens to be a pull request instead, Forgejo
will redirect as appropriate. With the `!1234` notation, a pull request
link will be created, which will be redirected to an issue if required.
However, this distinction could be important if an external tracker is
used, where links to issues and pull requests are not interchangeable.

## Actionable References in Pull Requests and Commit Messages

Sometimes a commit or pull request may fix or bring back a problem documented
in a particular issue. Forgejo supports closing and reopening the referenced
issues by preceding the reference with a particular _keyword_. Common keywords
include "closes", "fixes", "reopens", etc. This list can be
[customized](../../../admin/config-cheat-sheet/) by the
site administrator.

Example:

> This PR _closes_ [#1234](#)

If the actionable reference is accepted, this will create a notice on the
referenced issue announcing that it will be closed when the referencing PR
is merged.

For an actionable reference to be accepted, _at least one_ of the following
conditions must be met:

- The commenter has permissions to close or reopen the issue at the moment
  of creating the reference.
- The reference is inside a commit message.
- The reference is posted as part of the pull request description.

In the last case, the issue will be closed or reopened only if the merger
of the pull request has permissions to do so.

Additionally, only pull requests and commit messages can create an action,
and only issues can be closed or reopened this way.

The default _keywords_ are:

- **Closing**: close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved
- **Reopening**: reopen, reopens, reopened

## Time Tracking in Pull Requests and Commit Messages

When a commit or merging of a pull request results in automatic closing of an issue,
it is possible to also add the spent time resolving this issue through the commit message.

To specify spent time on resolving an issue, you need to specify time in the format
`@<number><time-unit>` after the issue number. In one commit message, you can specify
multiple fixed issues and spent time for each of them.

Supported time units (`<time-unit>`):

- `m` - minutes
- `h` - hours
- `d` - days (equals to 8 hours)
- `w` - weeks (equals to 5 days)
- `mo` - months (equals to 4 weeks)

Numbers to specify time (`<number>`) can be also decimal numbers, e.g., `@1.5h` would
result in one and a half hours. Multiple time units can be combined, e.g., `@1h10m` would
mean 1 hour and 10 minutes.

Example of commit message:

> Fixed #123 spent @1h, refs #102, fixes #124 @1.5h

This would result in 1 hour added to issue #123 and 1 and a half hours added to issue #124.

## External Trackers

Forgejo supports the use of external issue trackers, and references to issues
hosted externally can be created in pull requests. However, if the external
tracker uses numbers to identify issues, they will be indistinguishable from
the pull requests hosted in Forgejo. To address this, Forgejo allows the use of
the `!` marker to identify pull requests. For example:

> This is issue [#1234](#), and links to the external tracker.
> This is pull request [!1234](#), and links to a pull request in Forgejo.

The `!` and `#` can be used interchangeably for issues and pull requests _except_
for the case, where a distinction is required. If the repository uses an external
tracker, the commit message for a squash merge will use `!` as a reference marker by default.

## Issues and Pull Requests References Summary

This table illustrates the different kinds of cross-reference for issues and pull requests.
In the examples, `User1/Repo1` refers to the repository where the reference is used, while
`UserZ/RepoZ` indicates a different repository.

| Reference in User1/Repo1    | Repo1 issues are external | RepoZ issues are external | Should render                                           |
| --------------------------- | :-----------------------: | :-----------------------: | ------------------------------------------------------- |
| `#1234`                     |            no             |             -             | A link to issue/pull 1234 in `User1/Repo1`              |
| `!1234`                     |            no             |             -             | A link to issue/pull 1234 in `User1/Repo1`              |
| `#1234`                     |            yes            |             -             | A link to _external issue_ 1234 for `User1/Repo1`       |
| `!1234`                     |            yes            |             -             | A link to _PR_ 1234 for `User1/Repo1`                   |
| `User1/Repo1#1234`          |            no             |             -             | A link to issue/pull 1234 in `User1/Repo1`              |
| `User1/Repo1!1234`          |            no             |             -             | A link to issue/pull 1234 in `User1/Repo1`              |
| `User1/Repo1#1234`          |            yes            |             -             | A link to _external issue_ 1234 for `User1/Repo1`       |
| `User1/Repo1!1234`          |            yes            |             -             | A link to _PR_ 1234 for `User1/Repo1`                   |
| `UserZ/RepoZ#1234`          |             -             |            no             | A link to issue/pull 1234 in `UserZ/RepoZ`              |
| `UserZ/RepoZ!1234`          |             -             |            no             | A link to issue/pull 1234 in `UserZ/RepoZ`              |
| `UserZ/RepoZ#1234`          |             -             |            yes            | A link to _external issue_ 1234 for `UserZ/RepoZ`       |
| `UserZ/RepoZ!1234`          |             -             |            yes            | A link to _PR_ 1234 for `UserZ/RepoZ`                   |
| **Alphanumeric issue IDs:** |             -             |             -             | -                                                       |
| `AAA-1234`                  |            yes            |             -             | A link to _external issue_ `AAA-1234` for `User1/Repo1` |
| `!1234`                     |            yes            |             -             | A link to _PR_ 1234 for `User1/Repo1`                   |
| `User1/Repo1!1234`          |            yes            |             -             | A link to _PR_ 1234 for `User1/Repo1`                   |
| _Not supported_             |             -             |            yes            | A link to _external issue_ `AAA-1234` for `UserZ/RepoZ` |
| `UserZ/RepoZ!1234`          |             -             |            yes            | A link to _PR_ 1234 in `UserZ/RepoZ`                    |

_The last section is for repositories with external issue trackers that use an alphanumeric format._

_**-**: not applicable._

Note: automatic references between repositories with different types of issues (external vs. internal) are not fully supported
and may render invalid links.

# Profile customization

By default, the profile page of a user (or an organization) is the list of repositories they
own. It is possible to customize it with a short description that
shows to the left, under their avatar. It can also be fully
personalized with a markdown file that is displayed instead of the
list of repositories.

![Profile page](../../_images/user/profile/profile-step1.png)

It uses the `README.md` file from the `.profile` repository of the
user (or organization), if it exists.

If the `.profile` repository contains a [FUNDING file](../../repository/funding/) with
valid entries, then a "Donate" button is displayed as well, which users may
select to view a list of configured donation options for the user (or
organization).

Forked `.profile` repositories do not display profile content - only regular
`.profile` repositories (not forks) will be used to populate the profile page.
If you want to use content from a forked `.profile` repository, convert the
fork to a regular repository in the "Danger Zone" section of repository
settings.

![Profile README.md](../../_images/user/profile/profile-step2.png)

Making the `.profile` repository private will hide the Profile README and donation options.

Rather than supporting multiple social links on the profile card, under the user
avatar, such links - including
[`rel=me`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/me)
attributes - can be placed in the `.profile` README instead. This gives a user a
lot of flexibility on how they wish to display these links. To add a `rel=me`
attribute, the link should be written in HTML, rather than in Markdown format,
for example: `<a rel="me"
href="https://social.example.com/@username">Fediverse</a>`.

# Projects

A project is a [kanban board](<https://en.wikipedia.org/wiki/Kanban_(development)>) to organize issues.

![screenshot of the project page](../../_images/user/project/project.png)

## Card previews images

If the card preview in the project is set to **Images and Text**, the card preview displays images found in the corresponding issue. The most recent is displayed first, up to five images.

![example of card image preview](../../_images/user/project/project-image.png)

# Pull requests and Git flow

> **_TLDR:_** _Keep an eye on your repository and organization permissions. Don't take sweets from strangers. Use pull requests. Easy to review, easy to manage, and only the project maintainers/owners have permission to merge them._

Although it is perfectly possible to use a Git project on Codeberg just as a single shared central repository for individuals and teams, a collaborative workflow based on pull requests provides many benefits:

- The "hot" project repository requires only very few maintainers with full rights to sign off pull requests. Contributors can easily work on forked repositories.
- Each pull request collects the full edit history for a fix or feature branch. Contributors can squash this, or keep it, just as they prefer.

## Create a pull request

Let's say you would like to contribute to our "examples" project [knut/examples](https://codeberg.org/knut/examples).

First, fork the project you would like to work on by clicking the `Fork` button in the top-right corner of the project page:

![Fork a project](../../_images/user/pull-request/fork-button.png)

Then, clone it onto your local machine. We assume that [you have set up your SSH keys](https://docs.codeberg.org/security/ssh-key). This has to be done only once:

```shell
git clone git@codeberg.org:<YOURCODEBERGUSERNAME>/examples.git
```

Now, let's create a feature branch, make some changes, commit, push, edit, commit, push, ..., edit, commit, push:

```shell
git checkout -b feature
# make some changes
git commit -m "first feature"
git push    # here you get asked to set your upstream URL, just confirm
# do more work, edit...
git add new_file.png
git commit -m "second feature introducing a new file"
git push
# ...
git commit -m "more work, tidy-up"
git push
```

Now you can create a pull request by visiting the main repository page and clicking on the `New Pull Request` button.

![Create a pull request](../../_images/user/pull-request/pull-request-button.png)

This button is automatically shown if:

- You are the pusher on a branch that still exists and that is not the default branch.
- The push must have occurred within the last 6 hours.
- There is no open pull request for this branch.

## Reviews

On the pull request page, the `Files Changed` tab shows a `Review` button that can be used to approve the pull request or request changes.

![Review a pull request](../../_images/user/pull-request/pull-request-review-global.png)

Next to each changed line, a `plus` button allows you to add a comment on that specific line, for instance, to suggest a modification.

![Review comment on a line](../../_images/user/pull-request/pull-request-review-line.png)

To comment on several lines at once, hold <kbd>Shift</kbd>, press the `plus` button next to the first line of the range and drag down to its last line before releasing. The highlighted lines are then attached to a single comment.

![Multi-line review comment](../../_images/user/pull-request/pull-request-review-multiline.png)

When a pull request contains multiple commits, the button to the left of the `Review` button can be used to only review a single commit.

![Review a single commit](../../_images/user/pull-request/pull-request-review-commit.png)

## Code Suggestion

A review comment can carry a suggestion block: ` ```suggestion `. The comment will show the current lines and the proposed ones as a diff. Once applied, the suggestion will replace the commented lines.

![Suggest a change from the comment form](../../_images/user/pull-request/pull-request-suggestion-create.png)

To suggest a change, add a comment on the line and press the `±` button (`Add a suggestion`) of the editor toolbar. It will insert the block, prefilled with the current content of the line, ready to be edited. A block typed by hand works the same way.

A few rules apply:

- a suggestion on a multi-line comment replaces the whole commented range
- an empty block deletes the commented lines
- a comment holds at most one suggestion

![A suggestion rendered as a diff](../../_images/user/pull-request/pull-request-suggestion-apply.png)

### Applying suggestions

Anyone who can push to the head branch of the pull request sees an `Apply suggestion` button below the suggestion. Pressing it asks for a commit message and creates a commit on the head branch. The conversation will be resolved at the same time.

To apply several suggestions in one commit, use `Add suggestion to batch` on each of them. The `Commit suggestions` button at the top of the page creates a single commit while the `Discard` button empties the batch.

![Applying a batch of suggestions](../../_images/user/pull-request/pull-request-suggestion-batch.png)

The commit is attributed to the reviewer who wrote the suggestion, the person applying it will be the committer. When a batch mixes suggestions from several reviewers, the person applying it becomes the author and every reviewer is credited with a `Co-authored-by` trailer.

## Review requests and code owners

On the right-hand sidebar of the pull request page there is a `Reviewers` section. Clicking its title will open a dropdown menu using which you may request a review from one or more repository collaborators.

![Reviewers panel](../../_images/user/pull-request/pull-request-reviewers.png)

Review requests may additionally be automated by adding a `CODEOWNERS` file either to the root of the repository, or the `docs` or `.forgejo` directories.

Lines in this file represent rules. Each rule consists of a [Go-formatted regular expression](https://pkg.go.dev/regexp/syntax) matching paths to changed files, followed by the names of one or more users and/or organization teams, all separated by whitespaces. The regular expression may also be prepended with an exclamation mark (`!`) to create a negative rule.

Users are referenced by their usernames. Teams are referenced by the name of the organization, followed by a slash (`/`) and the name of the team. Both may optionally be prepended with an `@` sign.

When a pull request is submitted, the changes are checked against the rules in the `CODEOWNERS` file. When the path to any of the changed files matches the regular expression of a non-negative rule, a review of the pull request is automatically requested from all users and/or teams referenced by the rule. Negative rules do the opposite – reviews are requested when any of the changed files _do not match_ the regular expression.

A `CODEOWNERS` file may contain empty lines and comments, which begin with a hash (`#`) character.

An example `CODEOWNERS` file may look like this:

```
# Request review from User001 whenever anything in `src` changes
src/.* @User001

# Request review from the editors team in MyOrg whenever anything
# in `docs` changes
docs/.* @MyOrg/editors

# Request review from User002 whenever anything but `README.md` changes
!README\.md User002
```

## Keep it up-to-date: rebase pull requests to upstream

Sometimes the upstream project repository is evolving while we are working on a feature branch, and we need to rebase and resolve merge conflicts for upstream changes into our feature branch. This is not hard:

In order to track the `upstream` repository, we'll add a second remote that points to the original project. This has to be done only once:

```shell
git remote add upstream git@codeberg.org:knut/examples.git
```

You can also use the SSH variant here for public projects if you want to be
able to pull without specifying your credentials.

Now, let's pull from `upstream`, and rebase our local branch against the latest `HEAD` of the upstream project repository (e.g., the `main` branch):

```shell
git pull --rebase upstream main
git pull
```

That's it. You can now push your changes and create the pull request as usual by clicking on the "New Pull Request" button.

## A friendly note on owner rights and force push permissions

Please keep in mind that project owners can do _everything_, including editing and rewriting the history using `force-push`. In some cases, this is a useful feature
(for example, to undo accidental commits or clean up PRs),
but in most cases, a transparent history based on a pull-request-based workflow is surely preferable,
especially for the default branches of your project where other people rely on intact history.

> [!warning] Warning
> If you accidentally leaked sensitive data, say, leaked credentials,
> keep in mind that commits stay directly accessible, e.g., from the user
> activity tab or a Pull Request feed, for a while.
> Please contact your instance admin if you really need to remove such data from the public.

# Repository Permissions

When you invite collaborators to join your repository or when you create teams for your organization, you have to decide what each collaborator/team is allowed to do.

You can assign teams different levels of permission for each unit (e.g. issues, PRs, wiki).

## Profile and Visibility

The visibility of your repositories will depend on the visibility of your profile, as well as whether you have marked a repository as private. Let's break down what this means:

- If your profile's visibility is set to "Limited", _all_ of your non-private repositories will only be visible to logged-in users.
- If your profile's visibility is set to "Public", _all_ of your non-private repositories will be shown to everyone.
- If you do not want anyone (apart from your fellow collaborators) to see your repositories, mark your repository as "Private".

The visibility of your profile can be changed in the `Privacy settings`. Be careful when you set your profile's visibility to "Limited"; Even if a repository is public, users that are _not logged in_ will get a [404 error](https://en.wikipedia.org/wiki/HTTP_404) if they try to access your repository — it will seem as if it does not exist at all!

![screenshot showing the updated README](../../_images/user/repo-permissions/user-settings-privacy-limited.webp)

## Collaborators

There are four permission levels: **Read**, **Write**, **Administrator**, and **Owner**.

By default, the person who creates a repository is an **_Owner_**.

The table below gives an overview of what collaborators are allowed to do when granted each of these permission levels:

| Task                                                                                                                     | Read | Write | Admin | Owner |
| ------------------------------------------------------------------------------------------------------------------------ | ---- | ----- | ----- | ----- |
| View, clone, and pull repository                                                                                         | ✅   | ✅    | ✅    | ✅    |
| Contribute pull requests                                                                                                 | ✅   | ✅    | ✅    | ✅    |
| Push to/update contributed pull requests                                                                                 | ✅   | ✅    | ✅    | ✅    |
| Push directly to repository                                                                                              | ❌   | ✅    | ✅    | ✅    |
| Merge pull requests                                                                                                      | ❌   | ✅    | ✅    | ✅    |
| Moderate/delete issues and comments                                                                                      | ❌   | ✅    | ✅    | ✅    |
| Force-push/rewrite history (if enabled)                                                                                  | ❌   | ✅    | ✅    | ✅    |
| Add/remove collaborators to repository                                                                                   | ❌   | ❌    | ✅    | ✅    |
| Configure branch settings (protect/unprotect, enable force-push)                                                         | ❌   | ❌    | ✅    | ✅    |
| Configure repository settings (enable wiki, issues, PRs, releases, update profile)                                       | ❌   | ❌    | ✅    | ✅    |
| Configure repository settings in the danger zone (transfer ownership, delete wiki data / repository, archive repository) | ❌   | ❌    | ❌    | ✅    |

## Teams

The permissions for teams are quite configurable. You can specify which repositories a team has access to; therefore, you can specify for each unit (Code Access, Issues, Releases) a different permission level.

Each unit is configured to have one of these 3 permission levels:

- No Access: Members cannot view or take any other action on this unit when the repository is private.
- Read: Members can view the unit and perform standard actions for that unit (See the Read column under [Collaborators](#collaborators)).
- Write: Members can view the unit and execute write actions for that unit (See the Write column under [Collaborators](#collaborators)).

> [!note] Note
> All team members with access to a private repository are trusted
> with viewing partial data related to this repository on all units.
> For instance if a team is configured with **No Access** to issues,
> they will not be able to add a comment on an issue but they will
> be able to see the partial information about the issues when
> browsing the dashboard of the organization.

When a team is configured to have administrator access, this is specified, and you cannot change units. The team will have admin permissions (See the Admin column under _Collaborators_).

Currently, the following units that can be configured:

- Code: access source code, files, commits, and branches.
- Issues: organize bug reports, tasks, and milestones.
- Pull Requests: access pull requests and code reviews.
- Releases: track the project versions and downloads.
- Wiki: access and write documentation.
- Projects: access and manage issues and pull requests in project boards.
- Packages: access and manage [packages](../../packages).
- Actions: access and manage [Forgejo Actions](../../actions/overview).

There are also two units which can be toggled:

- External Wiki: access to external wiki.
- External Issues: access to the external issue tracker.

A team can be given the permission to create new repositories. When a member of such a team creates a new repository, they will get administrator access to the repository.

# Repository Activity

For each repository, the Activity tab displays insights about the recent changes. It helps figure out how lively it is and which contributors are the most active at a given moment in time.

## Contributors

![screenshot showing the contributors graph](../../_images/user/repository-activity/contributors.png)

The overall graph shows all commits over time and allows you to select a range of dates to get a more detailed view. The contributors are also displayed with their individual graphs. The contributor with the most commits is shown first.

## Pulse

![screenshot showing the pulse graph](../../_images/user/repository-activity/pulse.png)

A high-level view of the most recent commits and pull requests.

## Code frequency

![screenshot showing the code frequency graph](../../_images/user/repository-activity/code-frequency.png)

Shows the ratio of added and deleted lines over time.

## Recent commits

![screenshot showing the recent commits graph](../../_images/user/repository-activity/recent-commits.png)

The total number of commits over time.

# Email Settings

By default, Forgejo will send notifications to your registered email addresses.

This section only concerns your Forgejo account and will not affect the commits you make outside the website.
If you don't know how to **configure your name and email in Git**, take a look at the ["Configuring Git" man page](https://git-scm.com/docs/git-config).

## Configuring all notifications

To change your notification preferences, go to your [Account Settings](https://codeberg.org/user/settings/account) or manually navigate to the settings page.

You can access it by clicking on the menu button “Profile and Settings...” in the top-right corner of Forgejo. From this dropdown, click on Settings, then click on the Account tab.

In the section “Manage Email Addresses”, you can select one of the following options from the drop-down menu for each email address that you have registered with Forgejo:

| Option                      | Effect                                                                                                 |
| :-------------------------- | :----------------------------------------------------------------------------------------------------- |
| Enable Email Notifications  | Enables all notifications, except about your own actions (default setting)                             |
| And Your Own Notifications  | Enables all notifications, _including notifications about your own actions_                            |
| Only Email on Mention       | Forgejo will only send an email to this address if your username is mentioned in an issue or a comment |
| Disable Email Notifications | Forgejo will not send any emails to this address                                                       |

When you're finished, press the button “Set Email Preference” to confirm your selection.

> [!note] Note
> Disabling email notifications doesn't mean that you'll stop receiving important messages from the Forgejo organization.

## Issue notifications

As soon as you make a comment on an issue, you automatically subscribe to it. Unless you have disabled email notifications for all your email addresses, you will get an email for every change and comment on that issue.

You can check and modify your issue subscription status under the “Notifications” section on the menu on the right side of the issue screen.

## Watching repositories

When you watch a repository (by clicking on the “Watch” button in a repository), you will receive emails for every change (creation of issues, pull requests, comments, etc.) done in this repository.

To stop watching a repository, simply click on “Unwatch” in a repository.

To list all of your watched repositories, click on `/notifications/watching`. Alternatively, you can click the menu button “Profile and Settings...” in the top-right corner of Forgejo and click on "Subscriptions" in the dropdown.

# Your First Repository

Almost everything on Forgejo happens in a repository. Think of a repository as a home for your project, where all of its source code can be organized using Git, as well as where you can track issues and read and write wikis.

This article will guide you through creating your first repository on Forgejo, connecting your local development environment, and making your first commit.

## Creating a Repository

To create a new repository, you need to be logged in to your account.

When you're logged in, you can use one of the two buttons shown in the following screenshots to create a new repository:

![screenshot of the expanded menu in the upper right corner to create a new repository](../../_images/user/first-repository/create-repo.png)

This will lead you to the page below.

![screenshot of new repository page](../../_images/user/first-repository/create-repo-2.png)

Here's an explanation of the form's fields:

- **Owner** Here you can specify whether you want this to be your own personal project or whether you want it to be part of an organization that you're a part of.
- **Repository name** A name for your repository (which will also be part of its path, in this case `https://codeberg.org/knut/foobar`).
- **Visibility** Repositories are either _public_ or _private_. Public means that everyone will be able to access your repository, while your private repositories can only be accessed by you and your collaborators (see [Invite Collaborators](https://docs.codeberg.org/collaborating/invite-collaborators/)).
- **Description** A short description that appears next to your repository's name where appropriate.
- **Template** Occasionally you may want to generate your repository from an existing template. In that case, you can specify that template here. Otherwise, simply leave this field empty.
- **Issue Labels** If you want to initialize your project's issue tracker with a set of labels that you can use to categorize issues, you can choose one here. You don't have to choose this right away, though, as you can choose and modify issue labels at a later time as well.
- **.gitignore** A [.gitignore](https://git-scm.com/docs/gitignore) file defines which files Git should not keep track of. This is useful, for example, to prevent configuration files or binaries from being tracked in version control. You can choose to add a predefined file matching the programming language you use now, or add one manually later.
- **License** Here you can choose from a list of FSF/OSI approved licenses. A `LICENSE` file will then be added to the repository. For some help on choosing the correct license, have a look at our [licensing article](https://docs.codeberg.org/getting-started/licensing/).
- **README** is the first file one should read when accessing a repository. It's also the first file displayed when accessing a repository, a bit like the "homepage" of your repository. On Forgejo, this is interpreted as a [Markdown](https://docs.codeberg.org/markdown/) file.
- **Initialize repository** To add the `LICENSE`, `README`, and `.gitignore` files mentioned above to your new repository, make sure you tick this box.
- **Default branch** Using this field, you can choose how to name the default branch of your Git repository. We recommend you use the predefined default.
- **Object format** is the repository's object format. We recommend SHA1 as it's the most compatible. It cannot be changed later once the repository has been created.

It's okay to only specify the owner and repository name if you want to get started quickly.
After filling out the fields, click the green "Create Repository" button at the bottom of the page.

You should now see a screen similar to the one below. If you haven't chosen to generate `LICENSE`, `README`, and `.gitignore`, the screen might show instructions instead, which will vanish after [your first commit](#making-your-first-commit).

![screenshot showing a freshly baked repository](../../_images/user/first-repository/create-repo-3.png)

Here's what the most important buttons do:

- **Repository Settings (1)** is where you can make adjustments to your repository settings, such as setting a project website, changing the repository description, enabling/disabling a wiki and issue tracker, or deleting the repository. You may want to give this page a visit right now to get an overview of your options.
- **The RSS, Watch, Star, and Fork buttons (2)** allow you to interact with other people's repositories. While they don't do much for your own repository, when visiting another user's repository, you can click on "Watch" to get notified about everything important happening in that repository, "Star" to show the user your appreciation (and to help other users find interesting projects more quickly), and "Fork" to create your own copy of the repository, for example, to make modifications that you want to share with the original author. You can also use the RSS button to get the RSS feed of the repository.
- **The Repository Tabs (3)** contain links to every important feature within this repository (some may not be visible by default):
  - **Code** lets you browse through all versions of this repository's code.
  - **Issues** is a very important communication tool between the author, their users, and their contributors. Think of it as part bug-tracker, part forum.
    For more information on this, have a look at [The Basics of Issue Tracking](../issue-tracking-basics/).
  - **Pull Requests** is where other users can ask the author to "pull" in code, from a fork into the author's program.
  - **Releases** is a space where the author can upload finished versions of their program, for example binaries.
  - **Wiki** is a basic wiki feature built into Forgejo.
  - **Activity** calculates statistics about this repository.
- **Repository's Git URLs (4)** - use these to let Git know where to find this repository. Don't worry if you don't understand this now, we will look at this in the following section.

## Connect a local repository to Forgejo

After creating a new repository, as laid out in the previous section, you can now move on to connect the repository with your local development copy.

In this guide, we'll focus on connecting to Forgejo via HTTP using Git on the command line, but note that there are other ways to connect to Forgejo, as laid out in more detail in these articles:

- [Clone & Commit via CLI](https://docs.codeberg.org/git/clone-commit-via-cli/) and
- [Clone & Commit via Web](https://docs.codeberg.org/git/clone-commit-via-web/)

> Although we use HTTP in this guide, it is a good idea to set up SSH-based authentication once you feel confident to do so.

### Note on the different types of HTTP authentication

When you try to access a git repository over HTTP, you have 3 approaches for authentication. This does not apply to accessing a repository over SSH.

1. Using an API token as your HTTP password
   - In Settings -> Applications, click `New access token`. Give it any name, set access to `All`, and repository to `Read and write`, and click `Generate token`. Copy the long token that appears.
   - When operating on a repo and are prompted for a password, enter the API token as your password
   - See [Access Token Scope](../../authentication/token-scope/) for more advanced info.
2. Using the same username and password you use on your Forgejo instance's website. This is the simplest approach but has some downsides as seen below.
3. Using OAuth. This requires a git client with OAuth support, or the installation of a companion tool for the git binary. See [OAuth2 Git authentication](../../authentication/oauth2-provider/#git-authentication) for more info.

Comparison of options:

| Method                          | Tools needed                | Fast LFS? | Suitable for...                                                                                                                                                                                                                                                                    |
| ------------------------------- | --------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API token (Recommended)         | Any git tool                | Yes ✓     | Users who want a general approach that works with any git tool including the bare git binary, and without LFS issues.<br>Users who want to reduce the negative consequences if their git client credentials are leaked, since API tokens can be limited to repository access only. |
| OAuth (Recommended)             | Git tool with OAuth support | Yes ✓     | Users using a graphical git client with OAuth support, or who have the technical know-how to install a companion OAuth tool to the bare git binary.                                                                                                                                |
| Account's username and password | Any git tool                | No ✗      | Users who want a general approach that works with any git tool, but accept that LFS will be slow, and 2FA won't work.                                                                                                                                                              |

### Option A: Clone the newly created, empty repository

If you want to start a fresh project (so if you don't already have source code that you want to upload to Forgejo), the quickest way to get started is to clone your newly created repository like this:

#### 1. Navigate to your local workspace (optional)

If you're just getting started, it's a good idea to keep your projects neatly sorted in a dedicated directory, like in this example:

```bash
knut@iceberg:~$ mkdir repositories
knut@iceberg:~$ cd repositories
knut@iceberg:~/repositories$
```

#### 2. Clone the repository

To clone your newly created repository, execute `git clone` with the URL that is shown in your repository:

![screenshot of the two options to receive a clone URL for git](../../_images/user/first-repository/repo-url.png)

```bash
knut@iceberg:~/repositories$ git clone https://codeberg.org/knut/foobar
Cloning into 'foobar'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), 11.94 KiB | 764.00 KiB/s, done.
```

#### 3. Navigate to the cloned repository

After cloning, the repository should now be in a new directory with the same name as your repository. In this case, it's called `foobar`.

```bash
knut@iceberg:~/repositories$ cd foobar
knut@iceberg:~/repositories/foobar$ ls
LICENSE  README.md
```

### Option B: Connect an existing local source tree

If you have already written source code that you now would like to upload to Forgejo, follow the steps below. An upload to Forgejo is referred to as a `push`.

#### 1. Initialize a Git Repository

Unless you already have a Git Repository initialized in your local source tree, run `git init` in your project's directory:

```bash
knut@iceberg:~/my-project$ git init
Initialized empty Git repository in /home/knut/my-project/.git/
```

#### 2. Add Forgejo as the repository's origin

Now you need to tell Git where to push your changes. You would do that by specifying Forgejo as a remote.

> In this example, we'll specify Forgejo as the `origin` remote. You can name your remote any other way, especially if you already have an `origin`, but `origin` is the recommended name for the main remote repository.

```bash
knut@iceberg:~/my-project$ git remote add origin https://codeberg.org/knut/foobar
```

When you clone a repository from the Internet, the URL that you got your copy of the repository from will be automatically used for the `origin` remote.

If your local copy of the repository is missing some commits that exist in the remote repository, pushing will result in an error. There are two ways to fix this:

- Run `git pull` to combine your local changes with the changes that exist in the remote repository. If this does not work, please follow the instructions in your terminal.
- If you know what you are doing, you can also overwrite the remote repository using `git -f push`.
  This action will **permanently** alter your remote repository and is not suitable if you are working on a project together with other people.

### Option C: Migrate an existing git repository to Forgejo

You can migrate existing repos to Forgejo while preserving their entire history, tags, and more.

1. Navigate to your Foregejo instance's web interface
2. In the top right, click `+` -> `New migration`
3. Choose the provider you're importing from, and follow the instructions

## Making your first commit

Now that you've connected your repository to your local development copy, it's time to make your first commit.

> If you didn't generate the `LICENSE` and `README.md` files when creating the repository, it's a good idea to add them now. Just put them in your local development copy's directory and add them to your commit, as shown below.

#### 1. Create or edit a file

Let's modify the `README.md` file.

> If you didn't generate `README.md`, these commands will still work fine. However, instead of "Hello World!" being at the end of the file, it'll be the only line in the file.

```bash
knut@iceberg:~/repositories/foobar$ echo "Hello World!" >> README.md
knut@iceberg:~/repositories/foobar$ cat README.md
# foobar

Hello World!
```

Now, if you ask Git about your repository's status, you should see something similar to this:

```bash
knut@iceberg:~/repositories/foobar$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
```

You can create or modify more files, if you'd like.

#### 2. Add changes to your commit

Next, you should add each modified file that you'd like to add to your commit with `git add`:

```bash
knut@iceberg:~/repositories/foobar$ git add README.md
```

This command should output nothing.

> If you want to add all modified files in your source tree, you can run `git add .` but **be careful** as this might add unwanted files as well, so it's a good idea to double-check by running `git status` before committing.

#### 3. Commit your changes

By committing your changes, you create a new step in the version history of your program. They act like snapshots of your program's state at a given point in time, and you will later be able to jump back and forth between them.

> It is recommended to keep commits small and focused, so that, if necessary, they can be reverted or easily adapted into another context without too many side-effects.

To commit your changes, run `git commit` and specify a commit message; this will later help you (and others) to identify that commit when looking back at your version history.

```bash
knut@iceberg:~/repositories/foobar$ git commit -m "My first commit on Forgejo"
[main 1e12979] My first commit on Forgejo
 1 file changed, 1 insertion(+)
```

If you look at the version history of your program using `git log`, you may now see something similar to this:

```bash
knut@iceberg:~/repositories/foobar$ git log
commit 1e1297929c8c74d9a439fa71c1f0ffe1dbf3d5ad (HEAD -> main)
Author: Knut <knut@noreply.codeberg-test.org>
Date:   Sat Sep 26 14:01:00 2020 +0200

    My first commit on Forgejo

commit c75b50920e3aa7a7ab3484e898fb3ad77132722a (origin/main, origin/HEAD)
Author: Knut <knut@noreply.codeberg-test.org>
Date:   Sat Sep 26 12:29:57 2020 +0200

    Initial commit
```

#### 4. Push your changes to Forgejo

If you're happy with the changes you made, the next step is to present them to the world by pushing them to Forgejo:

```bash
knut@iceberg:~/repositories/foobar$ git push -u origin main
Username for 'https://codeberg.org': knut
Password for 'https://knut@codeberg.org':
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 16 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 315 bytes | 315.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: . Processing 1 references
remote: Processed 1 references in total
To https://codeberg.org/knut/foobar
   c75b509..1e12979  main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
```

The `-u` option sets the upstream remote, which we want to be Codeberg.org, as configured previously.

The `main` argument sets the name of the branch onto which shall be pushed upstream. For this example, it should be the same branch name that you specified when creating the repository.

When connecting via HTTPS, Git will ask you for your username and password, which you can enter interactively.

After refreshing the repository page, you should now see something similar to this:

![screenshot showing the updated README on Codeberg](../../_images/user/first-repository/hello-world.png)

Congratulations - you've just made your first source code contribution on Forgejo!

# Getting started

Your first steps on Forgejo.

# The Basics of Issue Tracking

Issues are an important means of communication on Forgejo.
Besides their original purpose - reporting bugs to a project's maintainer -
they can be used to suggest enhancements, ask for new features, discuss a project's
direction, ask questions, and much more.

## The Issue Tracker

![Issues list](../../_images/user/issue-tracking-basics/issues-list.png)

You can access the issue tracker of a project by clicking its "Issues" tab **(1)**.

There, you'll see a browsable and filterable **(2)** list of all issues.
Many projects use labels to categorize issues. If you want to contribute to a project,
a good label to look for is the "help wanted" label.

You can switch between issues that are still open and those that are already resolved **(3)**.

Some projects define milestones **(4)**, to which issues can be assigned. They are good for
visualizing the progress of a project's development.

You can create an issue by clicking on the orange "New Issue" button **(5)** at the top right
of the issues list.

Issues in the issue tracker are public, and everyone is able to read and answer them.

An issue can have an assignee, meaning someone responsible for resolving or moderating
the issue. Their profile picture, with a link to their profile, can be seen in the issue
list.

## Life of an Issue

Once an issue in the Issue Tracker has been created, it will usually pass through a
process of review, discussion, and closure, which can be more or less strictly defined,
based on the project you're contributing to.

The first thing that might happen is that your issue is categorized using labels.
Your issue may be reviewed by the project's maintainer(s) and evaluated whether it is, i.e., a bug report or feature request.

Then, depending on what type of issue it is, there might be additional questions
or a discussion and, if applicable, the implementation of a solution (or the rejection of
the issue).

Finally, the issue is closed and moved from the list of open issues to the closed one.
Issues might have dependencies on other issues or pull requests preventing them from being closed.

Occasionally, issues may become "stale". That's when there hasn't been any progress for
an extended period of time (usually months). You might consider reviving these, if there
is a strong interest in getting them resolved (and, preferably, if you can contribute
something to them).

> If you encounter an abandoned project and there's no way of contacting the maintainer(s),
> consider forking it, if you want to assume responsibility for it (or, rather, your fork).

## Things to Consider

### Security bugs

If the bug you have found has security implications, **do not create
an issue right away!** Instead, try contacting the project's maintainers privately.
Many projects have a dedicated email address for reporting security bugs. If the
project in question doesn't, consider writing an email directly to the project's
maintainer or asking for the address in the issue tracker.

> **⚠** What's important is that you **don't publicly expose security bugs before they are
> fixed _and_ the fixes are deployed**, because **otherwise, you might put the users of that
> project at severe risk**.

### Existing issues

Before creating a new issue, please make sure that there isn't already an existing
issue about, i.e., the bug you want to report or the feature you want to request.

If there already is an existing issue, please consider commenting on that issue instead,
if there is something more that you can contribute to it.

You should also make sure that the issue has not already been solved by having a look
at the closed issues **(3)** as well.

### Try to be precise and helpful

Project maintainers love precise information about why, i.e., a bug is happening.

Some projects may even have templates that specifically ask for information like
the operating system or database software used.

If you can provide that information, it will be easier for the project maintainer(s)
to quickly resolve your issue. And if you want it resolved even quicker,
consider writing a Pull Request solving the issue (if possible).

### Be (reasonably) patient

Please remember that many project maintainers work on their free software projects
in their free time. Some maintainers may answer you within minutes, others within days.
Don't be discouraged if there isn't an immediate answer.

# Integrated Wiki

A [wiki](https://en.wikipedia.org/wiki/Wiki) is a collaborative space on the web. It is a common practice to use wikis to collect knowledge and share information.
Codeberg allows you to add a wiki to a repository for additional documentation.

The user in these examples is `knut`, the polar bear, and its repository is `foobar`.

## Activation and Permissions

To enable the wiki for a repository, visit the `Settings` page and activate `Enable repository wiki` in the `Wiki` section under `Units`. It will default to the built-in wiki which is described here, but you can add a URI to an external site the "Wiki" tab should link to.

> [!warning] Warning
> Be aware that the wiki, once enabled, is accessible to _everyone_ who has
> `read` access to your repository - on public repositories even unauthenticated guests can access the wiki.
> The wiki is _not_ a suitable place for storing private information or secrets (like passwords).
>
> Activating the `Allow anyone to edit the wiki` option in `Settings` will give everyone with an account write access to the wiki.

To edit the wiki, `write` permission to the repository is required, unless the `Allow anyone to edit the wiki` setting is activated on the `Settings` page, in the `Wiki` section under `Units`.

## Wiki structure

The wiki is essentially a separate Git repository in your repository with a predefined name in the form of `<your-repository-name>.wiki.git`.

It consists of [Markdown](https://en.wikipedia.org/wiki/Markdown) files (file extension `.md`) and additional assets like images.
No further stylesheets are needed. The Markdown files are automatically rendered according to the selected Forgejo theme.

## Adding content via web

After you have enabled the wiki, you are prompted to create the initial page `Home.md`.

The web UI in your browser is currently limited to adding, updating, and deleting pages; you can't manage assets like images this way.

![Wiki home page with edit buttons](../../_images/user/wiki/wiki_pageview.png)

Clicking on the "Insert Image" button will make the following text appear in your text editor: `![](https://)`

## Adding content using a local Git client

You can work with the wiki repository as you would with any other Git repository on Forgejo.

```shell
git clone git@codeberg.org:knut/foobar.wiki.git
cd foobar.wiki
nano Home.md # or your editor of choice
git commit -am "create Home page"
```

Editing locally allows you to use your favorite editor (preferably with Markdown syntax check and highlighting) and manage additional assets like images.

### Adding images using a local Git client

You can add images to the root directory or a specific subfolder (like `assets` or `images`) using your local Git client.

A feasible workflow might look like this:

```shell
# create a subfolder for images
mkdir images
cd images
# copy the image into this folder
git add images/image.png
git commit -m "add image"
git push
```

## Attaching images in Markdown documents

Now, you can reference the image in Markdown, like this:

**File in repository**:

```markdown
![image alt text](images/image.png 'image title')
```

**External image**:

```markdown
![image alt text](https://example.com/image.jpg 'image title')
```

When including images from Forgejo repositories, keep in mind that _you should use the raw version of the image._

After saving your changes, the image should be visible.

> In contrast to embedding external images, images in Git are only rendered after saving the wiki or Markdown file changes.

## Adding a sidebar and a footer

To enhance the usability of your wiki, you can add a custom sidebar and a footer that are shown on every page. The sidebar will be displayed to the right of the main content and the footer below.

To enable the sidebar, just add a file named `_Sidebar.md` to your wiki. For a footer, the file must be named `_Footer.md`.
Both file types allow common Markdown syntax to adjust the presentation to your needs.

Very basic example for a sidebar:

```markdown
- [[Home]]

### Content

- [Page 1](./Page-1/)

> knuts wiki
```

> These files starting with `_` are hidden, so in the web UI you need to manually browse for the files. E.g. for our user _knut_ and his _foobar_ repo:
> `https://codeberg.org/knut/foobar/wiki/_Sidebar`

## Embedding LaTeX-style equations

The wiki also supports embedding LaTeX-style equations in Markdown files, using [KaTeX](https://katex.org).

Such equations go between two `$` characters, for example, like this:

```markdown
$ \frac{1}{2} = 0.5 $
```

This will then result in the following properly rendered equation:

![The $\frac{1}{2} = 0.5$ equation properly rendered by KaTeX](../../_images/user/wiki/katex_example.png)

It is also possible to have LaTeX render in display mode, using `$$` or `\[` with `\]`:

```markdown
\[ \mathbf{R} = \frac 1M \iiint_Q \rho(\mathbf{r}) \mathbf{r} \mathrm dV \]
$$ (p \implies q) \iff (\neg q \implies \neg p) $$
```

It is however not supported to use either of these syntaxes when the equation spans multiple lines; in that case, a code block with the language `math` should be used, for example:

````markdown
```math
\int_0^\pi\ln(1-2\alpha\cos x+\alpha^2) \mathrm dx =
2\pi\ln|\alpha|
```
````

# AGit Workflow Usage

Forgejo ships with custom support for [AGit-Flow](https://web.archive.org/web/20260114065059/https://git-repo.info/en/2020/03/agit-flow-and-git-repo/). Similarly to [Gerrit's workflow](https://www.gerritcodereview.com), this workflow provides a way of creating pull requests without having to create forks or feature branches (which require write permissions on the base repository).

## Changing git default push method

For the AGit-flow the [default push method of git](https://git-scm.com/docs/git-config#Documentation/git-config.txt-pushdefault) (`simple`) is unsuitable and should be changed to `upstream` (the target branch is usually different from the local branch):

```shell
# Only needed once per repo (add -g to set globally, for all your repos)
git config set push.default upstream
```

## Working with existing pull requests

You can fetch any pull request (fork-based or AGit-created), by knowing its number and choosing a local branch name (no need to add a new remote):

```shell
# replace 3 with the pull request number
# replace local-branch with your chosen branch name (will be created)
git fetch origin refs/pull/3/head:local-branch
git switch local-branch

# tell git that pull to this branch should fetch from this pull request
git config set branch.local-branch.merge refs/pull/3/head
```

**If the pull request was created using the AGit-flow** ([see below](#creating-pull-requests)), the pull request author and users with repository write permissions can push to this ref, after setting the remote:

```shell
# tell git to push to origin refs/pull/3/head (see branch.local-branch.merge above)
git config set branch.local-branch.remote origin
```

With the two branch configurations above, authorized users can run `git pull`, `git push` and `git push --force` against AGit pull requests.

## Creating pull requests

AGit pull request creation requires a dedicated `topic` (branch-like name), which will be visible online (prefixed with `username/`) and used to associate further commits with the same pull request.

After choosing the target branch of your pull request (`main` usually), you can push your changes and create a pull request in one command:

```shell
# replace main with the target branch
# replace humble-feature with your topic
git push origin HEAD:refs/for/main/humble-feature
```

The `HEAD` part of the [refspec](https://git-scm.com/book/en/v2/Git-Internals-The-Refspec) refers to the [checked out reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), but can be replaced with any ["local ref"](https://git-scm.com/docs/git-push), like a local branch name (e.g. `my-new-feature`):

```shell
git push origin my-new-feature:refs/for/main/humble-feature
```

### Setting a title and a description in AGit

It is possible to set additional parameters via git push options, such as `title` and `description`. Here's another example targeting the `dev` branch:

```shell
git push origin HEAD:refs/for/dev/implement-part12 \
  -o title="Title of the PR" \
  -o description="This can be **any** markdown content."
```

Both push options are optional. If no `title` or `description` push option is included, the title and the description of the first commit will be used instead.

New lines are not supported in the `description` push option. Multi-line descriptions can be encoded in base64 and prefixed with `{base64}` to work around this:

```shell
# encode the content of the file pr-description.md to base64
# for a description with multiple lines
git push origin HEAD:refs/for/main/cron \
  -o title="Update registry data" \
  -o description="{base64}$(base64 -w0 ./pr-description.md)"
```

### Updating an existing AGit pull request

To not have to specify the refspec for later pushes, you should configure `my-new-feature` branch in git:

```shell
# replace my-new-feature with your current branch name
# replace 4 with the pull request number
git config set branch.my-new-feature.merge refs/pull/4/head
git config set branch.my-new-feature.remote origin
```

This will enable running `git pull` and `git push`, as well as their variants like `git pull --rebase` or `git push --force` (`--force-with-lease` is unsupported though).

### Programmatically creating (or updating) a pull request

If you wish to programmatically open pull requests (for instance in CI), you can use the credentials of any user allowed to open pull requests (no need for writing rights on the base repository).

```shell
# git clone
# do your thing (check for update, generate code ...) and abort if nothing new
# set write-credentials of the user (who may not have writing rights for this repository):
git remote set-url origin https://$FORGEJO_USERNAME:$ACCESS_TOKEN@codeberg.org/forgejo/forgejo.git
# git add ...
# git commit -m "new update"
git push origin HEAD:refs/for/main/data-update \
  -o title="Update data" \
  -o description="{base64}$(base64 -w0 ./pr-description.md)" \
  -o force-push="true"
```

If an AGit pull request from this user with this topic is still open, the content will be overridden (like a force-push), otherwise a new pull request will be opened.
Without the `force-push` option, the workflow would fail if a previously created pull request was still open.

### Parameters reference

```sh
git push <remote-name> <local-ref>:refs/<for|draft|for-review>/<branch>/<topic> [-o <topic|title|description|force-push>]
```

The following parameters are available:

- `<remote-name>`: The name of the remote repository (e.g., `origin`) **(required)**
- `<local-ref>`: The local reference being pushed (e.g., `HEAD`, `my-branch`, a commit hash) **(required)**
- `refs/<for|draft|for-review>/<branch>/<topic>`: Refspec **(required)**
  - `for`, `draft`, `for-review`: This parameter describes the pull request type. **for** opens a normal pull request. **draft** and **for-review** are currently silently ignored.
  - `<branch>`: The target branch that a pull request should be merged against **(required)**
  - `<topic>`: The topic for the remote pull request. **If left empty,** the topic must be supplied using the `-o topic` option.
- `-o <topic|title|description|force-push>`: Push options
  - `topic`: Essentially an identifier. **If left empty,** the value of `<topic>`, if present, will be used for the topic. Otherwise, Forgejo will return an error.
  - `title`: Title of the pull request. **If left empty,** the first line of the first new Git commit will be used instead.
  - `description`: Description of the pull request. **If left empty,** the description of the first new Git commit will be used instead.
  - `force-push`: Useless when the [local branch has been configured](#updating-an-existing-agit-pull-request). Historically necessary when rebasing, amending, or [retroactively modifying](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) your previous commits. Otherwise, a new pull request will be opened, **even if you use the same topic**. If used, the value of this parameter should be set to `true`.

> [!note] Note
> A new pull request will be created if the `topic` was used in a pull request that is merged or closed, independently of the `force-push` parameter.

**For Gerrit users:** Forgejo does not support [Gerrit's Change-Ids](https://gerrit-review.googlesource.com/Documentation/user-changeid.html).

### Review Workflow

The web UI can be used to review AGit pull requests like any other.

To get command line instructions, click the "View command line instructions" drop-down at the bottom of the pull request form.

# Git CLI

Forgejo supports `git push` and `git pull`, but you can do more with the `git` command. Creating pull requests without the web interface, via [AGit](./agit-support/) or creating new repositories without the web interface, via [Push to Create](./push-to-create/).

# Push To Create

Push to create is a feature that allows you to push to a repository that does not yet exist in Forgejo. This is useful for automation and for allowing users to create repositories without having to go through the web interface. This feature is disabled by default.

## Enabling Push To Create

In the `app.ini` file, under the section `[repository]`, set `ENABLE_PUSH_CREATE_USER` to `true` and `ENABLE_PUSH_CREATE_ORG` to `true` if you want to allow users to create repositories in their own user account and in organizations they are members of, respectively. Restart Forgejo for the changes to take effect. You can read more about these two options in the Configuration Cheat Sheet.

## Using Push To Create

Assuming you have a git repository in the current directory, you can push to a repository that does not yet exist in Forgejo by running the following command:

```shell
# Add the remote you want to push to
git remote add origin git@{domain}:{username}/{repo name that does not exist yet}.git

# ensure default branch is named 'main'
git branch -M main

# push to the remote
git push -u origin main
```

This assumes you are using an SSH remote, but you can also use HTTPS remotes as well.

## Push options

Push-to-create will default to the visibility defined by `DEFAULT_PUSH_CREATE_PRIVATE` in `app.ini`. To explicitly set the visibility, there is support for some [push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt).

- `repo.private` (true|false) - Change the repository's visibility.

  This is particularly useful when combined with push-to-create.

- `repo.template` (true|false) - Change whether the repository is a template.

Example of changing a repository's visibility to public:

```shell
git push -o repo.private=false -u origin main
```

# Blame File View

Forgejo supports viewing the line-by-line revision history for a file, also known as blame view.
You can also use [`git blame`](https://git-scm.com/docs/git-blame) on the command line to view the revision history of lines within a file.

![Blame view of a file](../../_images/user/blame/view.png)

1. Navigate to and open the file whose line history you want to view.
2. Click the `Blame` button in the file header bar.
3. The new view shows the line-by-line revision history for a file with author and commit information on the left side.
4. To navigate to an older commit, click the icon.

## Ignore commits in the blame view

All revisions specified in the `.git-blame-ignore-revs` file are hidden from the blame view.
This is especially useful to hide reformatting changes and keep the benefits of `git blame`.
Lines that were changed or added by an ignored commit will be blamed on the previous commit that changed that line or nearby lines.
The `.git-blame-ignore-revs` file must be located in the root directory of the repository.
For more information, like the file format, see [the `git blame --ignore-revs-file` documentation](https://git-scm.com/docs/git-blame#Documentation/git-blame.txt---ignore-revs-fileltfilegt).

### Bypassing `.git-blame-ignore-revs` in the blame view

If the blame view for a file shows a message about ignored revisions, you can see the normal blame view by appending the URL parameter `?bypass-blame-ignore=true`.

# FUNDING files

Forgejo supports parsing a `FUNDING.yml` file found in a [repository](../../getting-started/first-repository/) and presenting its contents in various contexts as donation options for that repository. By default, Forgejo looks for a funding config document in the following directories on the repository's default branch, in this order:

1. `.forgejo`
2. `.github`
3. The repository root.

The filename of the funding config is case-insensitive, and may end with either `.yml` or `.yaml` (also case-insensitive). The first funding config found is considered the funding config for the repository. Other correctly-named files are not considered, even if the one found contains errors.

A `FUNDING.yml` document consists of key-value pairs of funding platform names and one or more string values. All Forgejo instances support at least the following funding platform names:

- `buy_me_a_coffee` ([Buy Me a Coffee](https://www.buymeacoffee.com/))
- `community_bridge` ([LFX Mentorship, formerly CommunityBridge](https://lfx.linuxfoundation.org/tools/mentorship))
- `github` ([GitHub Sponsors](https://github.com/sponsors))
- `issuehunt` ([IssueHunt](https://issuehunt.io/))
- `ko_fi` ([Ko-fi](https://ko-fi.com/))
- `liberapay` ([Liberapay](https://en.liberapay.com/))
- `open_collective` ([Open Collective](https://opencollective.com/))
- `patreon` ([Patreon](https://www.patreon.com/))
- `thanks_dev` ([thanks.dev](https://thanks.dev/))
- `tidelift` ([Tidelift](https://tidelift.com/))
- `custom` (Custom URL)

> [!note] Note for users migrating from GitHub
> At the time of writing, GitHub's own `FUNDING.yml` format [supports](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository#:~:text=Polar) an additional funding provider, `polar` ([Polar](https://www.polar.sh/)). Forgejo has opted not to include Polar support by default. See [forgejo/forgejo#13361 (comment)](https://codeberg.org/forgejo/forgejo/pulls/13361#issuecomment-20074045:~:text=polar%20has%20a%20similar%20situation) for details. If you wish to add a Polar payment page to your `FUNDING.yml`, use a `custom` URL instead.

Most of these accept a simple username or project name string to declare each funding entry. For example:

```yml
# FUNDING.yml
ko_fi: example1
liberapay: [example1, example2]
```

Some platforms have a more complex syntax. For example, `thanks_dev` requires a format like `u/gh/USERNAME`, and `tidelift` requires a pairing of `PLATFORM-NAME/PACKAGE-NAME`, where known-valid platform names include `npm`, `pypi`, `rubygems`, `maven`, `packagist`, and `nuget`.

```yml
# FUNDING.yml
thanks_dev: u/gh/example1
tidelift: npm/example1
```

Repository authors may add other custom donation URLs with `custom`:

```yml
# FUNDING.yml
custom: 'https://example.com/example1'
```

Instance administrators may [define other funding platforms](../../../admin/config-cheat-sheet/#repository-donation-links-funding) for use on their Forgejo instance.

The funding config may declare multiple entries for any given platform by providing a list of string values. For example:

```yml
# FUNDING.yml
ko_fi: [example1, example2]
custom:
  - 'https://example1.example.com'
  - 'https://example2.example.com'
```

A funding config may declare up to **15** total entries by default.

## Donate button

When the repository contains a funding config with at least one valid entry, a "Donate" button appears near the top-right corner of the project page:

![The "Donate" button, which features a colored heart icon, appears at the top of the repository view, between the "RSS" and "Watch" buttons.](../../_images/user/repo-funding/funding-button.png)

> [!tip] Tip
> You can add a "Donate" button to your user or organization profile by placing a funding config in the [`.profile`](../../collaboration/profile/) repository.

Selecting this button presents a modal which directs users to the various funding platforms described in the config:

![The modal sheet in this example is titled "Donate to test_user/test". Listed here are three links to funding pages for this project, including Liberapay, Ko-fi, and one custom URL on localhost.](../../_images/user/repo-funding/funding-modal.png)

The funding config used for the above example is as follows:

```yml
# FUNDING.yml
custom: 'http://localhost:3003/'
liberapay: example
ko_fi: example
```

## Errors

If Forgejo cannot understand one or more entries in a funding config, the error(s) are presented at the top of the file's web view. Affected entries are not shown in the Donate modal. If the funding config contains no valid entries, the Donate button does not appear.

![A FUNDING.yml document, as seen on Forgejo. Above the commit banner, a conspicuous error message states, "Error parsing funding config: Invalid type for key "custom", expected a string or string array".](../../_images/user/repo-funding/funding-error.png)

# Issue and Pull Request Templates

Some projects have a standard list of questions that users need to answer
when creating an issue or pull request. Forgejo supports adding templates to the
**default branch of the repository** so that they can autopopulate the form when users are
creating issues and pull requests. This will cut down on the initial back-and-forth
of getting some clarifying details.
It is currently not possible to provide generic issue/pull-request templates globally.
In general, there is the choice of using either multiple or a single issue template file(s), a single pull-request template file and additionally the possibility of adding an issue config.

Additionally, the New Issue page URL can be suffixed with `?title=Issue+Title&body=Issue+Text` and the form will be populated with those strings. Those strings will be used instead of the template if there is one.

## Directory names

Users can create multiple template files inside a special directory and this allows different use cases: users can choose to use the template that is suited to address their specific problem. Please note that there can be multiple issue templates. However, there may only be a single pull request template in one repository.

Forgejo will check for template files in the following directories, since Forgejo checks these services to make migration from them easier:

- `.forgejo`
- `.gitea`
- `.github`
- `docs`

Inside the directory can be multiple markdown (`.md`) or yaml (`.yaml`/`.yml`) files for templates of the form - these are the three allowed file extensions.

## File names

For the three file types - issue templates, pull request templates, and issue configs, there are various possible file names that may be used in the directories listed above.

**Issue templates** are arbitrarily named files with any of the three file extensions mentioned above, and there may be any number of them, so they each get their dedicated subdirectory: `issue_template` and `ISSUE_TEMPLATE` are the possible names for the subdirectory that the issue templates go into.

In contrast, there may only be a single pull request template per repository, so there is no extra subdirectory for it. It can reside in any of the `.<service_name>` directories (with the exception of the `.gitlab` directory, this is not supported!) listed in the previous section. A pull request template has a fixed name (see options below) and can end with any of the three file extensions mentioned above.

Possible file names and file extensions for **PR templates**:

- `PULL_REQUEST_TEMPLATE.md`
- `PULL_REQUEST_TEMPLATE.yaml`
- `PULL_REQUEST_TEMPLATE.yml`
- `pull_request_template.md`
- `pull_request_template.yaml`
- `pull_request_template.yml`

Issue config template files need to go into the same subdirectory as issue template files (see above section). Possible file names and file extensions for **issue config**:

- `config.yaml`
- `config.yml`

An example setup for a single issue template file together with a single pull request template file could look like this:

```
.forgejo/
  ISSUE_TEMPLATE/
    issue_template.yaml
  pull_request_template.yml
```

Another example setup but with multiple issue template files instead could look like this:

```
.forgejo/
  issue_template/
    bug.yaml
    config.yml
    feature.yaml
  pull_request_template.md
```

## Syntax for markdown template

```md
---
name: 'Template Name'
about: 'This template is for testing!'
title: '[TEST] '
ref: 'main'
labels:
  - bug
  - 'help needed'
---

This is the template!
```

In the above example, when a user is presented with the list of issues they can submit, this would show as `Template Name` with the description
`This template is for testing!`. When submitting an issue with the above example, the issue title would be pre-populated with
`[TEST] ` while the issue body would be pre-populated with `This is the template!`. The issue would also be assigned two labels,
`bug` and `help needed`, and the issue will have a reference to `main`.

## Syntax for yaml template

This example YAML configuration file defines an issue form using several inputs to report a bug.

```yaml
name: Bug Report
about: File a bug report
title: '[Bug]: '
ref: 'main'
labels:
  - bug
  - 'help needed'
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this bug report!
  # some markdown that will only be visible once the issue has been created
  - type: markdown
    attributes:
      value: |
        This issue was created by an issue **template** :)
    visible: [content]
  - type: input
    id: contact
    attributes:
      label: Contact Details
      description: How can we get in touch with you if we need more info?
      placeholder: ex. email@example.com
    validations:
      required: false
  - type: textarea
    id: what-happened
    attributes:
      label: What happened?
      description: Also tell us, what did you expect to happen?
      placeholder: Tell us what you see!
      value: 'A bug happened!'
    validations:
      required: true
  - type: dropdown
    id: version
    attributes:
      label: Version
      description: What version of our software are you running?
      options:
        - 1.0.2 (Default)
        - 1.0.3 (Edge)
    validations:
      required: true
  - type: dropdown
    id: browsers
    attributes:
      label: What browsers are you seeing the problem on?
      multiple: true
      options:
        - Firefox
        - Chrome
        - Safari
        - Microsoft Edge
  - type: textarea
    id: logs
    attributes:
      label: Relevant log output
      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
      render: shell
  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com)
      options:
        - label: I agree to follow this project's Code of Conduct
          required: true
        - label: I have also read the CONTRIBUTION.MD
          required: true
          visible: [form]
        - label: This is a TODO only visible after issue creation
          visible: [content]
```

### Markdown

You can use a `markdown` element to display Markdown in your form that provides extra context to the user, but is not submitted by default.

Attributes:

| Key   | Description                                                  | Required | Type   | Default | Valid values |
| ----- | ------------------------------------------------------------ | -------- | ------ | ------- | ------------ |
| value | The text that is rendered. Markdown formatting is supported. | Required | String | -       | -            |

visible: Default is **[form]**

### Textarea

You can use a `textarea` element to add a multi-line text field to your form. Contributors can also attach files in `textarea` fields.

Attributes:

| Key         | Description                                                                                                                                                                   | Required | Type   | Default      | Valid values                |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | --------------------------- |
| label       | A brief description of the expected user input, which is also displayed in the form.                                                                                          | Required | String | -            | -                           |
| description | A description of the text area to provide context or guidance, which is displayed in the form.                                                                                | Optional | String | Empty String | -                           |
| placeholder | A semi-opaque placeholder that renders in the text area when empty.                                                                                                           | Optional | String | Empty String | -                           |
| value       | Text that is pre-filled in the text area.                                                                                                                                     | Optional | String | -            | -                           |
| render      | If a value is provided, submitted text will be formatted into a codeblock. When this key is provided, the text area will not expand for file attachments or Markdown editing. | Optional | String | -            | Languages known to Forgejo. |

Validations:

| Key      | Description                                          | Required | Type    | Default | Valid values |
| -------- | ---------------------------------------------------- | -------- | ------- | ------- | ------------ |
| required | Prevents form submission until element is completed. | Optional | Boolean | false   | -            |

visible: Default is **[form, content]**

### Input

You can use an `input` element to add a single-line text field to your form.

Attributes:

| Key         | Description                                                                                | Required | Type   | Default      | Valid values |
| ----------- | ------------------------------------------------------------------------------------------ | -------- | ------ | ------------ | ------------ |
| label       | A brief description of the expected user input, which is also displayed in the form.       | Required | String | -            | -            |
| description | A description of the field to provide context or guidance, which is displayed in the form. | Optional | String | Empty String | -            |
| placeholder | A semi-transparent placeholder that renders in the field when empty.                       | Optional | String | Empty String | -            |
| value       | Text that is pre-filled in the field.                                                      | Optional | String | -            | -            |

Validations:

| Key       | Description                                                                                      | Required | Type    | Default | Valid values                                                             |
| --------- | ------------------------------------------------------------------------------------------------ | -------- | ------- | ------- | ------------------------------------------------------------------------ |
| required  | Prevents form submission until element is completed.                                             | Optional | Boolean | false   | -                                                                        |
| is_number | Prevents form submission until element is filled with a number.                                  | Optional | Boolean | false   | -                                                                        |
| regex     | Prevents form submission until element is filled with a value that match the regular expression. | Optional | String  | -       | a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) |

visible: Default is **[form, content]**

### Dropdown

You can use a `dropdown` element to add a dropdown menu in your form.

Attributes:

| Key         | Description                                                                                         | Required | Type         | Default      | Valid values |
| ----------- | --------------------------------------------------------------------------------------------------- | -------- | ------------ | ------------ | ------------ |
| label       | A brief description of the expected user input, which is displayed in the form.                     | Required | String       | -            | -            |
| description | A description of the dropdown to provide extra context or guidance, which is displayed in the form. | Optional | String       | Empty String | -            |
| multiple    | Determines if the user can select more than one option.                                             | Optional | Boolean      | false        | -            |
| options     | An array of options the user can choose from. Cannot be empty and all choices must be distinct.     | Required | String array | -            | -            |

Validations:

| Key      | Description                                          | Required | Type    | Default | Valid values |
| -------- | ---------------------------------------------------- | -------- | ------- | ------- | ------------ |
| required | Prevents form submission until element is completed. | Optional | Boolean | false   | -            |

visible: Default is **[form, content]**

### Checkboxes

You can use the `checkboxes` element to add a set of checkboxes to your form.

Attributes:

| Key         | Description                                                                                           | Required | Type   | Default      | Valid values |
| ----------- | ----------------------------------------------------------------------------------------------------- | -------- | ------ | ------------ | ------------ |
| label       | A brief description of the expected user input, which is displayed in the form.                       | Required | String | -            | -            |
| description | A description of the set of checkboxes, which is displayed in the form. Supports Markdown formatting. | Optional | String | Empty String | -            |
| options     | An array of checkboxes that the user can select. For syntax, see below.                               | Required | Array  | -            | -            |

For each value in the options array, you can set the following keys.

| Key      | Description                                                                                                                              | Required | Type         | Default | Options |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ------- | ------- |
| label    | The identifier for the option, which is displayed in the form. Markdown is supported for bold or italic text formatting, and hyperlinks. | Required | String       | -       | -       |
| required | Prevents form submission until element is completed.                                                                                     | Optional | Boolean      | false   | -       |
| visible  | Whether a specific checkbox appears in the form only, in the created issue only, or both. Valid options are "form" and "content".        | Optional | String array | false   | -       |

visible: Default is **[form, content]**

## Syntax for issue config

This is an example for an issue config file

```yaml
blank_issues_enabled: true
contact_links:
  - name: Forgejo
    url: https://forgejo.org/
    about: Visit the Forgejo Website
```

### Possible Options

| Key                  | Description                                           | Type               | Default     |
| -------------------- | ----------------------------------------------------- | ------------------ | ----------- |
| blank_issues_enabled | If set to false, the user is forced to use a template | Boolean            | true        |
| contact_links        | Custom links to show in the choose box                | Contact Link Array | Empty Array |

### Contact Link

| Key   | Description                      | Type   | Required |
| ----- | -------------------------------- | ------ | -------- |
| name  | the name of your link            | String | true     |
| url   | The URL of your link             | String | true     |
| about | A short description of your link | String | true     |

# Programming language detection

Forgejo tries to detect the languages used in a repository, for each file, to use this information in a number of different ways. The most prominent use is for the display of the language statistics line at the top of the repository's home view, but the same information is also used to exclude generated and vendored files from diffs.

![Repository language statistics](../../_images/user/language-detection/repo-languages.png)

This comes with sensible defaults that should work for most cases, but it is not without faults or compromises, and there are scenarios where the detection needs a little guidance. Further below, we will explain how it can be influenced, but let's look at the defaults first!

## Built-in language detection

Whenever the contents of a repository change, Forgejo will look at the files, and try to put them into one of the following categories: "Programming language", "Markup language", "Documentation", "Dotfile", "Configuration", "Generated", "Vendored". The sorting into categories is done by the [go-enry][enry] package, please consult its documentation for the specifics. The library is _mostly_ compatible with [linguist][linguist], which is used by some other forges. The statistics are calculated based on the main branch of the repository, only update when the main branch changes, and the update may take some time.

[enry]: https://github.com/go-enry/go-enry
[linguist]: https://github.com/github-linguist/linguist

For the repository language statistics, with the default configuration, only those files are considered that match the "Programming language" or "Markup" language categories, everything else is ignored. When viewing a diff, all files are shown, but "Generated" files are collapsed by default, and "Vendored" files are marked as such.

### A short explanation of categories

While some of the categories are rather straightforward, a little explanation about them does not hurt, especially if you are considering reclassifying files into another category.

**Vendored files** are any file you have checked into your repository that you didn't write, but imported from elsewhere. These may include dependencies you want a local copy of, at a specific version - such as JavaScript libraries or Go packages. These may inflate your project's language stats, and may even cause it to be labeled as another language. Marking these files as vendored makes it possible to ignore them for statistics.

**Generated files** are - as the name implies - files that are generated, but still checked into the repository for one reason or another. These may include minified JavaScript, compiled CoffeeScript, various lock files, and so on. Similar to vendored files, you usually do not want these to show up in language stats. They're - unlike vendored files - hidden by default when viewing diffs.

**Documentation** are just as the name says, documentation. This includes files written in Markdown, AsciiDoc, Org Mode, and a number of others.

**Configuration** files are typically used to configure software, and as such, the category is not included in language statistics. Languages like JSON, TOML, YAML, SQL, and XML - among other things - are considered documentation by default.

**Dotfiles** are files whose name starts with a dot, which by convention, suggests they should be hidden, and as such, they are excluded from language statistics.

**Programming languages** and **Markup languages** are more or less self-explanatory. The former category includes languages like C, Go, Rust, JavaScript, and many, many others. Markup languages are CSS, HTML, Jinja templates, Jupyter Notebooks, and numerous other formats.

Please consult the [enry][enry] or [linguist][linguist] documentation for more details.

## Adjusting the language detection

Sometimes the programming language of a file is not recognized properly, or it is miscategorized. Forgejo provides a mechanism where the language detection can be told about the language of a file, and its category can be adjusted as well. The same mechanism can also force a file to be considered for language statistics, regardless of its category - or the opposite, too: to tell Forgejo never to consider it.

The way to do this is via a [`.gitattributes`][gitattributes] file. This file has a simple syntax where each line is made up of a pattern, followed by a space separated list of attributes. There are many attributes that git itself supports, but we're only going to talk about the custom attributes for language detection. All of these have a `linguist-` prefix, as that is where they originate from.

[gitattributes]: https://git-scm.com/docs/gitattributes

### Overriding the language

In case Forgejo does not correctly recognize a file's language, you can use the `linguist-language` attribute to override its detection. The language names are case-insensitive, and may be specified using an alias. Spaces within language names must be replaced with hyphens.

An example showing these overrides:

```shell
# Reclassify `.pl` files as Prolog
*.pl     linguist-language=Prolog

# Whitespace in language names must be replaced with hyphens
*.glyphs linguist-language=OpenStep-Property-List

# Language names are case-insensitive, and may be specified using an alias.
# All of the following three lines are equivalent:
*.es     linguist-language=js
*.es     linguist-language=JS
*.es     linguist-language=JavaScript
```

### Overriding the category

It is possible to mark files matching a pattern as "Documentation", "Generated", or "Vendored". This can be useful when the automatic detection of these fail, or if you want to reclassify files for some other reason. It is also possible to reclassify files that would be considered either of these, as not being "Documentation", "Generated", or "Vendored".

To achieve this, the `linguist-documentation`, `linguist-generated`, and `linguist-vendored` attributes can be used. All three of these are boolean, you can set them simply by listing the attribute name. You can also set an explicit value by setting them to `true` or `false`. Prefixing the attribute with a minus sign is the same as setting it to `false`.

It's best to illustrate this with a few examples!

```shell
# Do not consider Markdown files documentation anymore!
# Note: Both forms here are equivalent.
*.md -linguist-documentation
*.md  linguist-documentation=false

# Consider files in `dist/` generated.
# Both forms here are equivalent.
/dist/**/* linguist-generated
/dist/**/* linguist-generated=true

# Do not categorize `cpplint.py` as vendored.
cpplint.py -linguist-vendored
cpplint.py  linguist-vendored=false
```

Reclassifying a file will result in Forgejo proceeding with language detection. That may still result in the file not being considered for statistics. Take a look at this example:

```shell
*.nib -linguist-generated
*.nib  linguist-language=Markdown
```

This will classify `*.nib` files as non-generated, and as Markdown. However, Markdown is considered documentation, which is, by default, excluded from language statistics.

### Overriding detection

In cases where a file should be considered for language statistics, regardless of its category, the `linguist-detectable` attribute can be used. The same attribute can be used to hide a file from the language statistics, without reclassifying it into a category that would otherwise be hidden.

For a repository whose primary contents are documentation in Markdown format, the following override would make Forgejo consider the Markdown files for the language statistics:

```shell
*.md linguist-detectable
```

Similarly, to hide a file from the language statistics:

```shell
config/app.js -linguist-detectable
```

The above will not consider the app's configuration - in JSON, but with a `.js` extension - for language statistics.

# Merge Message Templates

Possible file names for PR (Pull Request) default merge message templates:

- `.forgejo/default_merge_message/MERGE_TEMPLATE.md`
- `.forgejo/default_merge_message/REBASE_TEMPLATE.md`
- `.forgejo/default_merge_message/REBASE-MERGE_TEMPLATE.md`
- `.forgejo/default_merge_message/SQUASH_TEMPLATE.md`
- `.forgejo/default_merge_message/MANUALLY-MERGED_TEMPLATE.md`
- `.forgejo/default_merge_message/REBASE-UPDATE-ONLY_TEMPLATE.md`

Note: `.gitea` as the root directory is also recognized and kept only for compatibility reasons.

If Forgejo cannot find the file in the repository, it will search the _`CustomPath`_ for a folder named `default_merge_message` which may contain site-wide templates.

## Title/Body separation

The first line of the template creates the title, the rest makes the body of the message. Without a template, the default texts are:

- **Title** : `Merge pull request '[PR title]' ([PR reference]) from [head branch] into [base branch]`.
- **Body** : `Reviewed-on: [PR url]`.

You can customize the title and body of the default message following these rules:

- To replace each of those sections the first line of the template is used to replace the title and other lines the body.
- For the title, leading and trailing whitespace is trimmed
- For the body, trailing whitespace is trimmed from the last line
- If only the title is meant to be changed a single-line template can be used.
- To keep the default title but change the default body the template needs to start with a newline `\n` character.
- To remove the default title (make it empty) any white-space character can be inserted in the first line of the template, as white spaces are trimmed.

### Examples

Replace the default title and keep the default body:

```
Merge Title
```

Replace the default title and make the default body empty:

```
Merge Title\n

```

Keep the default title and replace the default body:

```
\n
Merge Body
```

The space in the first line makes the default title empty and replace the default body:

```
 \n
Merge body
```

## Variables

You can use the following variables enclosed in `${}` inside these templates, which follow [os.Expand](https://pkg.go.dev/os#Expand) syntax:

- BaseRepoOwnerName: Base repository owner name of this pull request
- BaseRepoName: Base repository name of this pull request
- BaseBranch: Base repository target branch name of this pull request
- HeadRepoOwnerName: Head repository owner name of this pull request
- HeadRepoName: Head repository name of this pull request
- HeadBranch: Head repository branch name of this pull request
- PullRequestTitle: Pull request's title
- PullRequestDescription: Pull request's description
- PullRequestPosterName: Pull request's poster name
- PullRequestIndex: Pull request's index number
- PullRequestReference: Pull request's reference character with index number, i.e., #1, !2
- ClosingIssues: A string containing all issues that will be closed by this pull request, i.e., `close #1, close #2`
- ReviewedOn: A string containing `Reviewed-on: <link>` with the link to the pull request
- ReviewedBy: Lines containing `Reviewed-by: <username> <<email>>` with the names and emails of the reviewers

## Rebase

When rebasing without a merge commit, `REBASE_TEMPLATE.md` modifies the message of the last commit. The following additional variables are available in this template:

- CommitTitle: Commit's title
- CommitBody: Commit's body text

# Branch and tag protection

Protected branches enforce restrictions such as force pushing or
merging unless a given number of approvals are obtained on a pull
request.

To protect a branch, you need to go to the repository’s **Settings** >
**Branches** page and add a new rule.

![Branch protection settings](../../_images/user/protection/branch-protect.png)

The name of the branch can be a glob where `/` is the separator and `**`
spans across separators, for instance `main`, `release/**` or `precious*`.

If two rules apply to the same branch, the one that has no glob takes
precedence.

## Protected tags

Protected tags allow control over who has permission to create or update Git tags. Each rule allows you to match either an individual tag name, or use an appropriate pattern to control multiple tags at once.

### Setting up protected tags

To protect a tag, you need to follow these steps:

1. Go to the repository’s **Settings** > **Tags** page.
2. Type a pattern to match a name. You can use a single name, a [glob pattern](https://pkg.go.dev/github.com/gobwas/glob#Compile) or a regular expression.
3. Choose the allowed users and/or teams. If you leave these fields empty, no one is allowed to create or modify this tag.
4. Select **Save** to save the configuration.

![Tag protection settings](../../_images/user/protection/tag-protect.png)

### Protected tag patterns

The pattern uses [glob](https://pkg.go.dev/github.com/gobwas/glob#Compile) or regular expressions to match a tag name. For regular expressions, you need to enclose the pattern in slashes.

Examples:

| Type  | Protected tag pattern    | Possible matching tags                  |
| ----- | ------------------------ | --------------------------------------- |
| Glob  | `v*`                     | `v`, `v-1`, `version2`                  |
| Glob  | `v[0-9]`                 | `v0`, `v1` up to `v9`                   |
| Glob  | `*-release`              | `2.1-release`, `final-release`          |
| Glob  | `forgejo`                | only `forgejo`                          |
| Glob  | `*forgejo*`              | `forgejo`, `2.1-forgejo`, `1_forgejo-r` |
| Glob  | `{v,rel}-*`              | `v-`, `v-1`, `v-final`, `rel-`, `rel-x` |
| Glob  | `*`                      | matches all possible tag names          |
| Regex | `/\Av/`                  | `v`, `v-1`, `version2`                  |
| Regex | `/\Av[0-9]\z/`           | `v0`, `v1` up to `v9`                   |
| Regex | `/\Av\d+\.\d+\.\d+\z/`   | `v1.0.17`, `v2.1.0`                     |
| Regex | `/\Av\d+(\.\d+){0,2}\z/` | `v1`, `v2.1`, `v1.2.34`                 |
| Regex | `/-release\z/`           | `2.1-release`, `final-release`          |
| Regex | `/forgejo/`              | `forgejo`, `2.1-forgejo`, `1_forgejo-r` |
| Regex | `/\Aforgejo\z/`          | only `forgejo`                          |
| Regex | `/^forgejo$/`            | only `forgejo`                          |
| Regex | `/\A(v\|rel)-/`          | `v-`, `v-1`, `v-final`, `rel-`, `rel-x` |
| Regex | `/.+/`                   | matches all possible tag names          |

# README badges

If the [badges service](../../../admin/config-cheat-sheet/#badges-badges) is enabled, Forgejo makes it possible to add badges - transparently generated by a service like `shields.io` - to your READMEs and other documentation. These badges let you display useful information about your repository within documentation that might also be displayed elsewhere, outside of the forge itself (such as on container information pages, library documentation sites, and so on). Information such as the latest release, stars, pull request and issue information, or [Actions workflow](../actions/) statuses.

The badges are returned in SVG format and are available under the `https://forgejo.example.com/{username}/{repository}/badges` path. Forgejo currently supports generating the following badges:

- `/release.svg`: The latest release made in the repository.
- `/stars.svg`: The number of stars the repository has.
- `/issues.svg`: The total number of issues in the repository, open and closed combined.
- `/issues/open.svg`: The number of open issues.
- `/issues/closed.svg`: The number of closed issues.
- `/pulls.svg`: The number of pull requests against the repository, open and closed combined.
- `/pulls/open.svg`: The number of open pull requests.
- `/pulls/closed.svg`: The number of closed pull requests.
- `/workflows/{workflow_file}/badge.svg`: The status of the `{workflow_file}` workflow. Additional restrictions can be given for this badge by using the `branch` and/or `event` query parameters. The `branch` query parameter will display the status of the workflow from the given branch (defaulting to the repository's main branch if not explicitly specified), while the `event` parameter will only display results if a run was triggered by the specified event.

All query parameters not known to Forgejo will be passed along to the badge generator service as is. If using the default - `shields.io`-based - generator, this means that query parameters such as `style` and `color` will be respected.

# Tags and Releases

Tags are a feature in Git that can be used to make a snapshot of a repository
from a point in time. It is generally used to mark releases (e.g., v1.2.4), and
it functions as a shortcut to see what the repo looked like at that time.

## What are releases?

Releases are a feature in Forgejo, independent of Git, that allows you to attach
files and release notes along with the source code at the time, and share it in
Forgejo, linking to a Git tag.

### Wait, what is the difference between tags and releases?

They are very similar. The difference is that tags are just the repository
frozen in time and are part of Git (you can make a tag inside of Git), but
releases are tags accompanied by a binary file and are not part of Git (you
need to go to your repository page in the web interface to create a release).

## Creating tags and releases

If you only want to create tags, using Git is recommended. If you want to create
a full release, this is only possible through Forgejo's web interface, or
through its API.

### Creating a Tag with Git

To create a tag using Git, use the following command in your local repository:

```bash
git tag -a <tag name> -m "<my tag message>"
```

You can omit `"<my tag message>"` to write a longer tag message in an editor
window.

> [!tip] Tip
> Tags are generally labeled by version numbers. It is good practice to
> prefix a version number with a `v` (e.g., `v1.2.3`) and to use the
> [Semantic Versioning](https://semver.org/) specification for assigning and
> incrementing version numbers.

Tags are not automatically pushed when you run `git push` (compared to commits
or branches). They have to be pushed manually to the remote target, like so:

```bash
git push --tags <remote target, probably "origin">
```

The argument `--tags` pushes all local tags to the remote target. If you want to
push only a specific tag, use:

```bash
git push <remote target, probably "origin"> <tag name, e.g., "v1.2.3">
```

### Creating a Release in the Web Interface

To create a release in the web interface, first go to the `Releases` tab of your
repository `(1)`, and click on `New Release (2)`:

![](../../_images/user/releases/1.png)

Here, you need to enter a version number for your new release `(1)`, select the
branch that contains the code you want to release `(2)`, and add a title `(3)`.
Optionally, you can also add a description `(4)` or add assets to the release in
the form of attaching files `(5)` or adding external links `(6)`:

![](../../_images/user/releases/2.png)

You can now either save it as a draft, or publish the release outright.

You are then redirected to the `Releases` tab of your repository. The newly
created release is now listed there:

![](../../_images/user/releases/3.png)

Here, you can edit the release if needed `(1)`. You will also see options to
download the source code of the repository at the commit the release was created
at as `.zip` or `.tar.gz` files `(2)`. Finally, if on the previous page you
added assets like an attached file or an external link, they will also show up
here `(3)`.

### Creating a Release through the API

All the operations accessible in the web interface are also available through
the API. You can use the following endpoints to:

- [`/repos/{owner}/{repo}/releases`](https://code.forgejo.org/api/swagger#/repository/repoCreateRelease) \
  Create (or modify) a release
- [`/repos/{owner}/{repo}/releases/{id}/assets`](https://code.forgejo.org/api/swagger#/repository/repoCreateReleaseAttachment) \
  Create (or modify) release assets

# Webhooks

Forgejo supports webhooks for repository events. This can be configured on the settings
page `/:username/:reponame/settings/hooks` by a repository admin. Webhooks can also be configured on a per-organization and whole-system basis (default webhooks on repository creation or instance-wide webhooks).
The currently supported webhook types are:

### Raw event as JSON or FORM payload:

- Forgejo (GET or POST)
- Gitea (GET or POST)
- Gogs (POST)

### Dedicated integration:

- Packagist: ask Packagist to refresh the given package
- SourceHut Builds: submit build manifests (only on push)

### Generic notification messages in chosen channel/chat:

- Matrix
- Slack
- Discord
- Dingtalk
- Telegram
- Microsoft Teams
- Feishu / Lark Suite
- WeCom (Wechat Work)

These channels don't implement all webhook types.
Those types that are implemented have to be manually tested.
Try them and open an [issue](https://codeberg.org/forgejo/forgejo/issues/new/choose) if they do not work for your use case.

### Event information

The following is an example of event information that will be sent by Forgejo to
a Payload URL:

```
X-Forgejo-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
X-Forgejo-Event: push
X-GitHub-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
X-GitHub-Event: push
X-Gogs-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
X-Gogs-Event: push
X-Gitea-Delivery: f6266f16-1bf3-46a5-9ea4-602e06ead473
X-Gitea-Event: push
```

```json
{
  "ref": "refs/heads/develop",
  "before": "28e1879d029cb852e4844d9c718537df08844e03",
  "after": "bffeb74224043ba2feb48d137756c8a9331c449a",
  "compare_url": "http://localhost:3000/forgejo/webhooks/compare/28e1879d029cb852e4844d9c718537df08844e03...bffeb74224043ba2feb48d137756c8a9331c449a",
  "commits": [
    {
      "id": "bffeb74224043ba2feb48d137756c8a9331c449a",
      "message": "Webhooks Yay!",
      "url": "http://localhost:3000/forgejo/webhooks/commit/bffeb74224043ba2feb48d137756c8a9331c449a",
      "author": {
        "name": "Forgejo",
        "email": "someone@forgejo.org",
        "username": "forgejo"
      },
      "committer": {
        "name": "Forgejo",
        "email": "someone@forgejo.org",
        "username": "forgejo"
      },
      "timestamp": "2017-03-13T13:52:11-04:00"
    }
  ],
  "repository": {
    "id": 140,
    "owner": {
      "id": 1,
      "login": "forgejo",
      "full_name": "Forgejo",
      "email": "someone@forgejo.org",
      "avatar_url": "https://localhost:3000/avatars/1",
      "username": "forgejo"
    },
    "name": "webhooks",
    "full_name": "forgejo/webhooks",
    "description": "",
    "private": false,
    "fork": false,
    "html_url": "http://localhost:3000/forgejo/webhooks",
    "ssh_url": "ssh://forgejo@localhost:2222/forgejo/webhooks.git",
    "clone_url": "http://localhost:3000/forgejo/webhooks.git",
    "website": "",
    "stars_count": 0,
    "forks_count": 1,
    "watchers_count": 1,
    "open_issues_count": 7,
    "default_branch": "master",
    "created_at": "2017-02-26T04:29:06-05:00",
    "updated_at": "2017-03-13T13:51:58-04:00"
  },
  "pusher": {
    "id": 1,
    "login": "forgejo",
    "full_name": "Forgejo",
    "email": "someone@forgejo.org",
    "avatar_url": "https://localhost:3000/avatars/1",
    "username": "forgejo"
  },
  "sender": {
    "id": 1,
    "login": "forgejo",
    "full_name": "Forgejo",
    "email": "someone@forgejo.org",
    "avatar_url": "https://localhost:3000/avatars/1",
    "username": "forgejo"
  }
}
```

### Example

This is an example of how to use webhooks to run a PHP script upon push requests to the repository.
In your repository Settings, under Webhooks, set up a Forgejo webhook as follows:

- Target URL: http://example.com/webhook.php
- HTTP Method: POST
- POST Content Type: application/json
- Secret: 123
- Trigger On: Push Events
- Active: Checked

Now, on your server, create the PHP file `webhook.php`.

```
<?php

$secret_key = '123';

// check for POST request
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    error_log('FAILED - not POST - '. $_SERVER['REQUEST_METHOD']);
    exit();
}

// get content type
$content_type = isset($_SERVER['CONTENT_TYPE']) ? strtolower(trim($_SERVER['CONTENT_TYPE'])) : '';

if ($content_type != 'application/json') {
    error_log('FAILED - not application/json - '. $content_type);
    exit();
}

// get payload
$payload = trim(file_get_contents("php://input"));

if (empty($payload)) {
    error_log('FAILED - no payload');
    exit();
}

// get header signature
$header_signature = isset($_SERVER['HTTP_X_FORGEJO_SIGNATURE']) ? $_SERVER['HTTP_X_FORGEJO_SIGNATURE'] : '';

if (empty($header_signature)) {
    error_log('FAILED - header signature missing');
    exit();
}

// calculate payload signature
$payload_signature = hash_hmac('sha256', $payload, $secret_key, false);

// check payload signature against header signature
if ($header_signature !== $payload_signature) {
    error_log('FAILED - payload signature');
    exit();
}

// convert json to array
$decoded = json_decode($payload, true);

// check for json decode errors
if (json_last_error() !== JSON_ERROR_NONE) {
    error_log('FAILED - json decode - '. json_last_error());
    exit();
}

// success, do something
```

There is a Test Delivery button in the webhook settings that allows testing the configuration as well as a list of the most Recent Deliveries.

### Authorization header

Forgejo webhooks can be configured to send an [authorization header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) to the target.

![screenshot of the webhook definition webpage](../../_images/user/webhooks/authorization-header.png)

The authentication string is stored encrypted in the database.

# Code Search

Forgejo supports code search through an indexer and `git-grep` as a fallback when [`REPO_INDEXER_ENABLED`](../../../admin/config-cheat-sheet#indexer-indexer) is disabled.

## Basic (git-grep)

![Code search results page using git-grep](../../_images/user/code-search/gitgrep.png)

When `REPO_INDEXER_ENABLED` is set to `false`, code search is restricted to a single repository, utilizing the [`git-grep`](https://git-scm.com/docs/git-grep) command.

### Supported Options

The following options are currently available for code search while using `git-grep`.

- **Exact**: Perform an exact match on the provided expression.
- **Union**: Conduct a union match, returning results that contain at least one of the specified keywords. For example, a search query containing `hello world` will yield results with either `hello` or `world`.
- **RegExp**: Utilize the provided regular expression to perform a pattern-based match (matches will not be highlighted).

### Scope

Since `git-grep` is performed on the fly, it can be executed on any valid branch or tag. The currently active branch/tag is displayed as the default value in the dropdown menu above the search bar, allowing users to easily switch between branches and tags.

Searching within a specific directory (or file) executes `git-grep` using a [literal pathspec](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) for the given path. If `REPO_INDEXER_INCLUDE` has been set by the administrator, the filter is added if and only if it matches one of the globs.

#### Example

Performing a search for `foo` at `/{user}/{repo}/src/branch/main?path=src` returns results that belong to the branch `main` inside the directory `/src`.

```
main
├── docs -> [...]
└── src
    └── main.go
    └── utils.go
```

In the above figure, the search would match results for `foo` in `main.go` and `utils.go`, but not from `docs/*`.

## Indexer

![Code search results page using indexer](../../_images/user/code-search/indexer.png)

For complex searches or cross-repository queries across an entire organization or instance, `REPO_INDEXER_ENABLED` must be set to `true`.
This enables code search via the selected indexer ([`REPO_INDEXER_TYPE`](../../../admin/config-cheat-sheet#indexer-indexer)).

### Supported Options

The following options are currently available for code search while using an indexer.

- **Exact**: Perform an exact match on the provided expression.
- **Union**: Conduct a union match, returning results that contain at least one of the specified keywords. For example, a search query containing `hello world` will yield results with either `hello` or `world`.
- **Fuzzy** (_if enabled by the administrator_): Conduct a fuzzy search, returning results that contain the keyword within a maximum edit distance of 2. For example, a search query containing `hello` will yield results with:
  - **edit distance of 0**: `hello`
  - **edit distance of 1**: For example, `hllo` (delete), `helloo` (add), `hallo` (modify).

> [!note] Note
> Fuzzy search requires the admin to set [`REPO_INDEXER_FUZZY_ENABLED`](../../../admin/config-cheat-sheet#indexer-indexer).

### Scope

Please note that when using the repository indexer, search results are limited to the contents of the default branch of each repository.

Similar to basic search, searching within a directory (or file) is also possible for advanced search.
However, unlike basic search, the search is more granular as it applies the filter but selectively includes/excludes files depending on `REPO_INDEXER_INCLUDE`/`REPO_INDEXER_EXCLUDE`.

# Issue Search

Since v11, Issue search on Forgejo replaced its previously dropdown-defined behavior of searches with a query syntax defined by operators.

## Query Syntax

### Keywords

1. Terms

Performs a fuzzy match search of each term in the query.

For example,

- `foo` will return results that contain `foo`, or any of its derivatives (such as `for` or `food`)
- `foo bar` will return results that contain `foo` and/or `bar` or any of their derivatives

2. Phrase

Performs an exact match for the provided phrase. This is done by wrapping the phrase in double quotes.

For example,

- `"foo"` will return results that contain an exact match of `foo`
- `"foo bar"` will return results that contain an exact match of `foo bar`

### Operators

1. Negation

Negates the keyword by returning results that do not contain the keyword. This is done by prefixing the keyword with a minus symbol.

For example,

- `-foo` will return results that do not contain `foo` or its derivatives
- `-"foo bar"` will return results that do not contain `foo bar`

2. Required

Denotes that the provided keyword MUST be present. This is done by prefixing the keyword with a plus symbol.

For example,

- `+foo +bar` will return results that contain both `foo` and `bar` (or its fuzzy derivatives)
- `+"foo" +"bar"` will return results that contain exactly both `foo` and `bar`
- `+"foo bar" +"baz"` will return results that contain exactly both `foo bar` and `baz`

### Escaping

All operators may be escaped by adding a backslash in front of the operators.

For example,

- `"\"\""` will search for `""`
- `\+1` will search for `+1`

## Filters

Filters listed below may be escaped by using a phrase search. For example, `"is:open"`.

### State

These filters are mutually exclusive.
If multiple state filters are used, then the rightmost filter takes precedence.

|          Filter           |                         Description                         |
| :-----------------------: | :---------------------------------------------------------: |
| `is:open` or `-is:closed` |         Filters issues/pull requests that are open          |
| `is:closed` or `-is:open` |        Filters issues/pull requests that are closed         |
|         `is:all`          | Filters issues/pull requests that are either open or closed |

Examples,

- `is:open +"bug"`: Searches for `bug` among open issues/PRs.
- `is:all -"backport"`: Search for all issues/PRs that do not contain the term "backport".
- `"is:all"`: Literal search for `is:all`. Matches Issues/PRs that contain the term `is:all`.

### User

These filter issues/pull requests by a username.

|    Filter    |    Description    |
| :----------: | :---------------: |
|  `author:*`  |    Authored By    |
| `assignee:*` |    Assigned To    |
|  `review:*`  |    Reviewed By    |
| `mentions:*` | Mentions the user |

Examples,

- `author:forgejo is:all`: All issues/PRs created by user `forgejo`.

### Misc.

|         Filter         |                                      Description                                       |
| :--------------------: | :------------------------------------------------------------------------------------: |
| `sort:<by>:[asc/desc]` | Sorts issues/PRs. Where, `<by>` is one of `created`, `comments`, `updated`, `deadline` |
| `modified:[>/<]<date>` |                            Last updated date in ISO format                             |

Examples,

- `sort:created:desc is:all`: Sort by the latest issues/PRs.
- `sort:comments:desc`: Sort by the most commented issues.
- `modified:>2025-12-05`: Searches for issues/PRs modified after `2025-12-05`.
- `modified:2025-12-05`: Searches for issues/PRs modified exactly on `2025-12-05`.
- `modified:<2025-12-05`: Searches for issues/PRs modified before `2025-12-05`.