GitHub-to-GitLab migration the easy way GitLab announced that migrating from GitHub to GitLab is easier than ever with its built-in project importer and GitLab Duo AI assistant, which converts GitHub Actions workflows into GitLab CI/CD. The importer automatically transfers most project data, including repositories, issues, pull requests, comments, labels, milestones, and wiki pages, while collaborators, markdown attachments, large comment volumes, Git LFS objects, and secrets require manual handling or caveats. GitLab Duo handles the traditionally manual CI/CD translation, and user contribution mapping (GitLab 17.8+) simplifies attribution without requiring matching public email addresses. Updated on: August 18, 2026 12 min read Learn how easy it is to migrate from GitHub to GitLab using GitLab's project importer and GitLab Duo AI. If you're evaluating a move to GitLab, the first question is almost always the same: How hard is the migration? For most DevSecOps https://about.gitlab.com/topics/devsecops/ teams, the fear of "heavy lifting" is the biggest obstacle between them and a single, AI-native DevSecOps platform https://about.gitlab.com/platform/ . Migrating to GitLab is easier than ever. GitLab's built-in importer moves the vast majority of your project data automatically and in the background. Additionally, translating your CI/CD pipeline, one part that has traditionally required a human, is now largely handled by GitLab Duo https://about.gitlab.com/gitlab-duo/ . Duo, our AI-native assistant, can convert GitHub Actions workflows into GitLab CI/CD. In this post, I'll walk through the full migration end to end: Let's get started. GitLab's built-in GitHub importer https://docs.gitlab.com/user/project/import/github/ is accessed directly from GitLab's project creation UI, and it runs as a background job, meaning you can kick off an import and walk away. Most of your project data comes across automatically. A smaller set of items are optional toggles or have caveats worth knowing about up front. The table below breaks down what migrates and how: | Data | Status | |---|---| | Git repository, branches, tags, and commit history | ✅ Automatic. Includes fork branches for open pull requests. | | Issues | ✅ Automatic | | Pull requests become merge requests | ✅ Automatic. Includes reviews, review comments, suggestions, assigned reviewers, and "merged by" info. | | Issue and pull request comments | ✅ Automatic | | Labels and milestones | ✅ Automatic | | Release notes content | ✅ Automatic | | Wiki pages | ✅ Automatic | | Branch protection rules | ✅ Automatic | | Issue and pull request events | ✅ Automatic | | Collaborators members | ⚠️ With caveats. Optional toggle on by default . Requires the read:org scope; GitHub roles are mapped to GitLab roles see below . GitHub Enterprise Cloud custom roles aren't supported and are added manually. | | Markdown attachments in descriptions, comments, releases | ⚠️ With caveats. Optional toggle. Attachments in private repos from before May 2023 can't be imported a GitHub limitation ; GitHub Enterprise Server imports only images and video. | | Large comment volumes ~30,000+ | ⚠️ With caveats. Enable the alternative comments import to work around GitHub's per-issue API limits. Comments from before 2017 may import as separate threads. | | Git LFS objects | ⚠️ With caveats. LFS must be enabled on the destination project before the import runs, or objects are silently skipped. | | GitHub Actions workflows | 🛠️ Manual AI-assisted . Converted to .gitlab-ci.yml . GitLab Duo can do most of this for you. | | Secrets → CI/CD variables | 🛠️ Manual. Recreate secrets as | GitHub and GitLab use different naming conventions, so a mapping is performed during migration. When collaborators are imported, GitHub roles map to GitLab roles as follows: | GitHub role | GitLab role | |---|---| | Read | Guest | | Triage | Reporter | | Write | Developer | | Maintain | Maintainer | | Admin | Owner | The prerequisites have gotten simpler over the years. Notably, you no longer need every GitHub author to have a matching public email address on GitLab. GitLab now handles attribution automatically with user contribution mapping https://docs.gitlab.com/user/project/import/ user-contribution-and-membership-mapping GitLab 17.8+ . More on that below. To import from GitHub.com or GitHub Enterprise Server into either GitLab.com or a GitLab Self-Managed instance, you need: A couple of situational prerequisites: read:org scope on your token and at least Write or Maintain access on the GitHub project.Previously, contribution history only carried over cleanly if each GitHub user's public email matched their GitLab email. Today, GitLab creates placeholder users for any GitHub author, assignee, or reviewer without a matching GitLab account, and preserves their contributions. After the import, a group Owner or Maintainer goes to Members → Placeholders and reassigns each placeholder to the real GitLab user, who then accepts the reassignment. This means you can migrate first and sort out attribution afterward. There are three ways to run the import. Pick the one that fits your source and scale. This is the fastest path for most teams. Optional toggles: | Toggle | Default | Use it when | |---|---|---| | Import collaborators | On | You want project members brought over with role mapping. | | Import Markdown attachments | Off | You want images and files embedded in descriptions, comments, and releases. | | Use alternative comments import | Off | Your project has ~30,000+ comments and you're hitting GitHub API limits. | If OAuth isn't configured, authenticate with a token instead. github.com/settings/tokens/new with the repo scope add read:org if you're importing collaborators or LFS . Note: Fine-grained tokens aren't supported.For migrating many repositories at once, scripting a move, or importing public repos you don't own, use the import API https://docs.gitlab.com/api/import/ . curl --request POST \ --url "https://gitlab.com/api/v4/import/github" \ --header "PRIVATE-TOKEN: $GITLAB TOKEN" \ --header "Content-Type: application/json" \ --data '{ "personal access token": "