diff --git a/action.yml b/action.yml
index 789f367..4fc807c 100644
--- a/action.yml
+++ b/action.yml
@@ -21,7 +21,7 @@ description: |
   The `update` script is expected to be found in the origin repository
   running the PR. It is given four arguments:
 
-    * A directory in which the destination repository is checked-out
+    * A directory in which the destination repository (or a fork) is checked-out
       on the base branch
     * A file with the JSON describing the pull request in the
       destination repository
@@ -36,6 +36,9 @@ description: |
   When the PR is from a forked repository, the `update` script is checked out from
   the default branch instead of the head branch of the fork.
 
+  If the fork of the destination repository is specified and it does
+  not exist, it is created.
+
 inputs:
   origin-url:
     description: 'URL of the Forgejo instance where the PR that triggers the action is located (e.g. https://code.forgejo.org)'
@@ -55,6 +58,8 @@ inputs:
   destination-repo:
     description: 'the repository in which the cascading PR is created or updated'
     required: true
+  destination-fork-repo:
+    description: 'the fork of {desitnation-repo} in which the {destination-branch} will be created or updated'
   destination-branch:
     description: 'the base branch of the destination repository for the cascading PR'
     required: true
@@ -102,6 +107,7 @@ runs:
                         --origin-pr "${{ inputs.origin-pr }}" \
                         --destination-url "${{ inputs.destination-url }}" \
                         --destination-repo "${{ inputs.destination-repo }}" \
+                        --destination-fork-repo "${{ inputs.destination-fork-repo }}" \
                         --destination-token "@$destination_token" \
                         --destination-branch "${{ inputs.destination-branch }}" \
                         --update "${{ inputs.update }}" \
diff --git a/cascading-pr-lib.sh b/cascading-pr-lib.sh
index 5cf0c39..3a8d00e 100644
--- a/cascading-pr-lib.sh
+++ b/cascading-pr-lib.sh
@@ -115,6 +115,18 @@ function scheme() {
     echo "${url%%://*}"
 }
 
+function owner() {
+    local repo="$1"
+
+    echo "${repo%%/*}"
+}
+
+function repository() {
+    local repo="$1"
+
+    echo "${repo##*/}"
+}
+
 function get_status() {
     local api="$1"
     local sha="$2"
diff --git a/cascading-pr.sh b/cascading-pr.sh
index 2f6ed9f..5f8fec7 100755
--- a/cascading-pr.sh
+++ b/cascading-pr.sh
@@ -64,21 +64,6 @@ EOF
     log_info "comment added to $(pr_url origin)"
 }
 
-function upsert_destination_branch() {
-    if $(exists_branch destination) ; then
-	log_info "branch ${options[destination_head]} already exists"
-	return
-    fi
-    cat > $TMPDIR/data <<EOF
-{
-  "new_branch_name":"${options[destination_head]}",
-  "old_branch_name":"${options[destination_base]}"
-}
-EOF
-    repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/branches
-    log_info "branch ${options[destination_head]} created"
-}
-
 function pr_destination_title() {
     echo "cascading-pr from ${options[origin_url]}/${options[origin_repo]}/pulls/${options[origin_pr]}"
 }
@@ -94,13 +79,18 @@ function upsert_destination_pr() {
 	log_info "an open PR already exists $url"
 	return
     fi
+    if ${options[destination_is_fork]} ; then
+	head="$(owner ${options[destination_fork_repo]}):${options[destination_head]}"
+    else
+	head=${options[destination_head]}
+    fi
     local title=$(pr_destination_title)
     cat > $TMPDIR/data <<EOF
 {
   "title":"$(pr_destination_title)",
   "body":"$(pr_destination_body)",
   "base":"${options[destination_base]}",
-  "head":"${options[destination_head]}"
+  "head":"$head"
 }
 EOF
     retry repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/pulls > $TMPDIR/destination-pr.json
@@ -165,26 +155,55 @@ function pr_from_fork() {
     pr $1 | jq --raw-output .head.repo.fork
 }
 
-function upsert_clone() {
-    local direction=$1 ref="$2" clone=$3
+function git_clone() {
+    local direction=$1 url=$2
 
     if ! test -d $TMPDIR/$direction; then
-	git -c credential.helper="store --file=$TMPDIR/$direction.git-credentials" clone $clone $TMPDIR/$direction
+	git -c credential.helper="store --file=$TMPDIR/$direction.git-credentials" clone $url $TMPDIR/$direction
     fi
     (
 	cd $TMPDIR/$direction
-	if [[ "$ref" =~ ^refs/ ]] ; then
-	    git fetch origin +$ref:$ref
-	else
-	    ref=origin/$ref
-	fi
-	git checkout -b $direction $ref
 	git config credential.helper "store --file=$TMPDIR/$direction.git-credentials"
 	git config user.email cascading-pr@example.com
 	git config user.name cascading-pr
     )
 }
 
+function git_checkout() {
+    local direction=$1 ref="$3"
+    local remote=origin
+
+    (
+	cd $TMPDIR/$direction
+	if [[ "$ref" =~ ^refs/ ]] ; then
+	    git fetch ${remote} +$ref:$ref
+	else
+	    ref=${remote}/$ref
+	fi
+	git checkout -b prbranch $ref
+    )
+}
+
+function git_remote() {
+    local direction=$1 remote=$2 url=$3
+
+    (
+	cd $TMPDIR/$direction
+	git remote add $remote $url
+    )
+}
+
+function git_reset_branch() {
+    local direction=$1 remote=$2 branch=$3
+    (
+	cd $TMPDIR/$direction
+	if git ls-remote --exit-code --heads ${remote} $branch ; then
+	    git fetch --quiet ${remote} $branch
+	    git reset --hard ${remote}/$branch
+	fi
+    )
+}
+
 function sha_pushed() {
     local direction=$1
     if test -f $TMPDIR/$direction.sha ; then
@@ -193,13 +212,13 @@ function sha_pushed() {
 }
 
 function push() {
-    local direction=$1 branch=$2 clone=$3
+    local direction=$1 remote=$2 branch=$3
 
     (
 	cd $TMPDIR/$direction
 	git add .
 	if git commit -m 'cascading-pr update'; then
-	    git push --force origin $direction:$branch
+	    git push --force ${remote} prbranch:$branch
 	    git rev-parse HEAD > ../$direction.sha
 	    log_info "pushed"
 	else
@@ -214,9 +233,53 @@ function wait_destination_ci() {
     wait_success $repo_api $sha
 }
 
+function upsert_fork() {
+    if repo_curl ${options[destination_repo]} api_json ${options[destination_fork_api]} > $TMPDIR/fork.json 2> /dev/null ; then
+	if test "$(jq --raw-output .fork)" != true ; then
+	    log_error "the destination fork already exists but is not a fork ${options[destination_fork]}"
+	    return 1
+	fi
+	local forked_from_repo=$(jq --raw-output .parent.full_name)
+	if test "$forked_from_repo" != "${options[destination_repo]}" ; then
+	    log_error "${options[destination_fork]} must be a fork of ${options[destination_repo]} but is a fork of $forked_from_repo instead"
+	    return 1
+	fi
+    else
+	local fork_owner=$(owner ${options[destination_fork]})
+	local data="{}"
+	if repo_curl ${options[destination_repo]} api_json ${options[destination_url]}/api/v1/orgs/${fork_owner} >& /dev/null ; then
+	    data='{"organization":"'$fork_owner'"}'
+	fi
+	repo_curl ${options[destination_repo]} api_json --data "$data" ${options[destination_url]}/api/v1/${options[destination_repo]}/forks
+    fi
+}
+
+function checkout() {
+    #
+    # origin
+    #
+    git_clone origin ${options[origin_clone]}
+    git_checkout origin "${options[origin_head]}"
+
+    #
+    # destination
+    #
+    git_clone destination ${options[destination_clone]}
+    git_checkout destination "${options[destination_base]}"
+
+    #
+    # fork
+    #
+    local head_remote=origin
+    if ${options[destination_is_fork]} ; then
+	upsert_fork
+	git_remote destination fork ${options[destination_fetch_fork]}
+	head_remote=fork
+    fi
+    git_reset_branch destination $head_remote "${options[destination_head]}"
+}
+
 function update() {
-    upsert_clone origin "${options[origin_head]}" ${options[origin_clone]}
-    upsert_clone destination "${options[destination_head]}" ${options[destination_clone]}
     (
 	local update=${options[update]}
 	if ! [[ "$update" =~ ^/ ]] ; then
@@ -234,7 +297,11 @@ function update() {
 	cd $TMPDIR
 	$update $TMPDIR/destination $TMPDIR/destination-pr.json $TMPDIR/origin $TMPDIR/origin-pr.json
     )
-    push destination ${options[destination_head]} ${options[destination_clone]}
+    local remote_head=origin
+    if ${options[destination_is_fork]} ; then
+	remote_head=fork
+    fi
+    push destination $remote_head ${options[destination_head]}
 }
 
 function set_clone() {
@@ -254,12 +321,22 @@ function set_clone() {
     options[${direction}_clone]=${options[${direction}_scheme]}://${options[${direction}_host_port]}/${options[${direction}_repo]}
 }
 
+function fork_sanity_check() {
+    local fork_repo=${options[destination_fork_repo]}
+    local repo=${options[destination_repo]}
+    if test "$(repository $fork_repo)" != "$(repository $repo)"; then
+	echo "$repo and its fork $fork_repo must have the same repository name (see https://codeberg.org/forgejo/forgejo/issues/1707)"
+	return 1
+    fi
+}
+
 function finalize_options() {
     options[origin_api]=${options[origin_url]}/api/v1/repos/${options[origin_repo]}
     options[origin_scheme]=$(scheme ${options[origin_url]})
     options[origin_host_port]=$(host_port ${options[origin_url]})
     set_clone origin
     options[origin_head]=refs/pull/${options[origin_pr]}/head
+
     options[destination_api]=${options[destination_url]}/api/v1/repos/${options[destination_repo]}
     options[destination_scheme]=$(scheme ${options[destination_url]})
     options[destination_host_port]=$(host_port ${options[destination_url]})
@@ -267,6 +344,14 @@ function finalize_options() {
     options[destination_base]=${options[destination_branch]}
     : ${options[prefix]:=${options[origin_repo]}}
     options[destination_head]=${options[prefix]}-${options[origin_pr]}
+
+    if test "${options[destination_fork_repo]}"; then
+	fork_sanity_check
+	options[destination_is_fork]=true
+	options[destination_fork_api]=${options[destination_url]}/api/v1/repos/${options[destination_fork_repo]}
+	options[destination_is_fork]=false
+    fi
+
     : ${options[close_merge]:=false}
 }
 
@@ -279,7 +364,7 @@ function run() {
     case "$state" in
 	open)
 	    log_info "PR is open, update or create the cascade branch and PR"
-	    upsert_destination_branch
+	    checkout
 	    update
 	    local sha=$(sha_pushed destination)
 	    if test "$sha" ; then
@@ -297,6 +382,7 @@ function run() {
 		    log_info "PR was merged, update the cascade PR"
 		    pr_get origin
 		    pr_get destination
+		    checkout
 		    update
 		fi
 	    else
@@ -351,6 +437,11 @@ function main() {
 		options[destination_repo]=$1
 		shift
 		;;
+	    --destination-fork-repo)
+		shift
+		options[destination_fork_repo]=$1
+		shift
+		;;
 	    --destination-token)
 		shift
 		options[destination_token]=$1