Connect Bubble to GitHub using the API Connector plugin with a Personal Access Token in a Private shared header. Build developer-facing tools inside Bubble — issue trackers, PR dashboards, repo browsers, or GitHub Actions dispatch buttons — while all API calls stay server-side. Important clarification: Bubble's native version control (Settings → Versions) is NOT Git and does not create Git commits; it is Bubble's own proprietary branching system.
| Fact | Value |
|---|---|
| Tool | Git |
| Category | DevOps & Tools |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 45–75 minutes |
| Last updated | July 2026 |
Two things people mean when they search 'Bubble + Git'
When founders search for 'Bubble Git integration', they usually mean one of two very different things — and it is important to address both directly.
The first question is: 'Can I version-control my Bubble app with Git?' The honest answer is no. Bubble apps live in Bubble's proprietary cloud infrastructure, not in code files. There is no Bubble CLI that exports your app to a folder of files you can commit to a Git repository. Bubble's own version control system — found at Settings → Versions — is a proprietary snapshot system that uses the word 'branch' but has nothing to do with Git commits, diffs, or pull requests. It is Bubble's internal way of saving app states and branching, and it does not sync to any external Git repository.
The second question — and the one this guide answers — is: 'Can I connect my Bubble app to GitHub's API to build developer tools?' The answer is yes. The GitHub REST API at https://api.github.com gives you access to repositories, issues, pull requests, commits, GitHub Actions, and more. Bubble's API Connector can read and write this data using a Personal Access Token (PAT) stored safely in a Private server-side header.
This makes Bubble a surprisingly capable platform for building internal developer portals: a ticket/issue tracker, a PR review dashboard for your team, a release management UI, or a deploy trigger that dispatches a GitHub Actions workflow. All API calls exit from Bubble's servers, so tokens with write scopes stay protected.
Note: if you need individual users to connect their own GitHub accounts (not just your team's admin token), an OAuth App flow is required — this is more complex and covered in the advanced troubleshooting section.
Integration method
API Connector targeting the GitHub REST API (https://api.github.com) with a Personal Access Token (PAT) stored in a Private shared header — all calls are server-side proxied by Bubble.
Prerequisites
- A Bubble app on any plan (API Connector works on all Bubble plans including Free); paid plan required only if you need Backend Workflows for server-side GitHub automations
- A GitHub account with access to the repositories you want to connect
- A GitHub Personal Access Token (classic or fine-grained) with the appropriate scopes for your use case (see Step 1)
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble)
Step-by-step guide
Understand Bubble's own version control vs. Git — and create a GitHub PAT
Before writing a single configuration line, take two minutes to understand an important conceptual distinction that saves confusion later. Bubble's Settings → Versions is NOT Git. When you click 'Create a new version' or 'Fork the current version' in Bubble's settings, you are creating a snapshot of your Bubble app's state stored in Bubble's proprietary database. This is Bubble's own branching system — useful for testing risky changes — but it produces no Git commits, no diffs, and no connection to any external repository. Do not look for a way to sync Bubble versions to GitHub; no such sync exists. Now, to connect Bubble to GitHub's API, you need a Personal Access Token (PAT). There are two types: Classic PATs and Fine-grained PATs. For most developer portal use cases, a Classic PAT is simpler: Go to GitHub.com → click your profile photo → Settings → scroll to Developer settings (left sidebar) → Personal access tokens → Tokens (classic) → Generate new token (classic). Give the token a descriptive name like 'Bubble App Integration'. Set an expiration date (90 days is a good default — do not set no expiration for security reasons). Select scopes based on what you need: - `repo` — read and write access to private repositories (issue creation, PR management) - `public_repo` — if you only need public repo access (safer, lower risk) - `issues` — read/write issues (included in `repo`) - `actions:write` — if you want to trigger GitHub Actions workflows from Bubble - `read:org` — if you need to list org repos Click 'Generate token'. GitHub shows the token ONCE — copy it immediately to a secure location (password manager). You will not be able to see it again. For fine-grained PATs (more secure), go to Tokens (beta) → Generate new token → select your repository and specific permissions. Fine-grained PATs expire at most 1 year and restrict access to specific repos, making them a better long-term choice for production apps.
Pro tip: Use the minimum scope needed — if your Bubble app only reads issues and displays repo info, use 'public_repo' or a fine-grained PAT with read-only Issues permission instead of the broad 'repo' scope. Narrower scopes limit blast radius if the token is ever compromised.
Expected result: You have a GitHub PAT copied to a secure location and understand which scopes it carries. You understand that Bubble's Settings → Versions is unrelated to Git.
Install the API Connector and configure a Private GitHub auth header
In your Bubble editor, click the Plugins tab in the left sidebar. Click 'Add plugins', search for 'API Connector' (by Bubble), and install it — it is free. Click 'API Connector' in your Plugins list to open the configuration panel. Click 'Add another API' and name it 'GitHub'. In the Shared headers section, click 'Add header'. GitHub's REST API accepts two auth header formats for PATs: - Classic PATs: `Authorization: token ghp_yourtokenhere` - Fine-grained PATs and OAuth tokens: `Authorization: Bearer github_pat_yourtokenhere` For the Key field, enter `Authorization`. For the Value field, enter `token ghp_yourtokenhere` (classic) or `Bearer github_pat_yourtokenhere` (fine-grained) — replacing the placeholder with your actual token. Check the 'Private' checkbox on this header row. This is the most important step: the Private flag ensures Bubble's server never sends this header value to the browser, never logs it in Bubble's Logs tab, and never exposes it in the Bubble debugger. Also add a second shared header: Key = `Accept`, Value = `application/vnd.github.v3+json`. This tells GitHub's API you want the v3 JSON response format — without it some endpoints return unexpected data. Leave the Authentication dropdown set to 'None or self-handled' since you are handling auth manually via the header.
1{2 "method": "GET",3 "url": "https://api.github.com/user",4 "headers": {5 "Authorization": "token <github_pat - Private>",6 "Accept": "application/vnd.github.v3+json"7 }8}Pro tip: GitHub recently started returning deprecation warnings for the 'token ' format in favor of 'Bearer '. If you see warnings in the API response body, switch to 'Bearer ' prefix — both formats work for Classic PATs as of mid-2026 but Bearer is the forward-compatible choice.
Expected result: The 'GitHub' API group shows in the API Connector with a Private Authorization shared header and the Accept header configured.
Create an initialize call to verify authentication
Before building your main data calls, create a simple initialize call to confirm your PAT is working correctly. Click 'Add another call' under the GitHub API group. Name it 'Get Authenticated User'. Set the type to GET. Enter the URL: `https://api.github.com/user`. Set 'Use as' to 'Data'. This endpoint returns information about the authenticated user — login, name, avatar_url, public_repos count, etc. Click 'Initialize call'. Since this call has no dynamic parameters, click 'Send' immediately. If the call succeeds (HTTP 200), Bubble will detect fields: login (text), name (text), avatar_url (text), public_repos (number), created_at (date). These become a Bubble data type you can use in expressions. If you see a 401 Unauthorized response, your PAT is incorrect, expired, or the header format is wrong. Check the Authorization header value — ensure it starts with 'token ' or 'Bearer ' with a space before the token string. If you see 403 Forbidden, the PAT has been revoked or lacks the required scope. Now create your primary data calls. For listing issues: name = 'List Issues', type = GET, URL = `https://api.github.com/repos/<owner>/<repo>/issues`, add query parameters `state` (text, value: 'open'), `per_page` (number, value: 30), `page` (number, value: 1). Set 'Use as' to 'Data'. Initialize with real values for owner, repo, and you should get back a list of issue objects. For triggering GitHub Actions: name = 'Dispatch Workflow', type = POST, URL = `https://api.github.com/repos/<owner>/<repo>/actions/workflows/<workflow_id>/dispatches`, body = `{"ref": "<branch>"}`. Set 'Use as' to 'Action'. GitHub returns 204 No Content on success — Bubble may show an empty response, which is correct.
1GET https://api.github.com/repos/<owner>/<repo>/issues?state=open&per_page=30&page=<page>23POST https://api.github.com/repos/<owner>/<repo>/actions/workflows/<workflow_id>/dispatches4Content-Type: application/json56{7 "ref": "<branch_name>"8}Pro tip: Use '/user' (GET /user) as your initialization and auth-check call — it is the simplest call in the GitHub API, requires minimal scope, and always returns a consistent shape. If this call works, your auth header is correct.
Expected result: GET /user returns your GitHub profile data with HTTP 200. You can see your login, name, and avatar_url fields detected in Bubble. Issue list call returns an array of GitHub issues.
Build a Repeating Group to display paginated GitHub data
Bubble's Repeating Group is the right UI component for displaying lists of GitHub data — issues, PRs, repos, or commits. Proper pagination is essential because GitHub limits most endpoints to 30 results per page by default (max 100 with `per_page=100`). In your Bubble editor, click the Design tab and add a Repeating Group element to your page. In the Repeating Group's property panel: - Set Type of content to the GitHub data type you want to display (e.g., 'List Issues') — this should be a data type Bubble detected from your initialize call. - Set Data source to 'Get data from an external API' → select your 'GitHub - List Issues' call. - Map the dynamic parameters: owner = your GitHub org name (hardcoded or from a Bubble input), repo = the repo name, per_page = 30, page = a custom state (see below). For pagination, create a custom state on the page (or on a Group element) called 'current_page' of type Number with a default value of 1. Set the `page` parameter in the Repeating Group's data source to this custom state's value. Add 'Previous' and 'Next' buttons below the Repeating Group. On Next click, add a Workflow action: 'Set state' → current_page = current_page + 1. On Previous click: Set state → current_page = MAX(current_page - 1, 1) to prevent going below page 1. Inside the Repeating Group's cells, add text elements bound to issue fields: - Title text: 'Current cell's List Issues' → title - Number text: '#' + 'Current cell's List Issues' → number - State badge: 'Current cell's List Issues' → state - Date text: 'Current cell's List Issues' → created_at → formatted as 'MMM D, YYYY' GitHub returns date fields as ISO 8601 strings. Bubble typically parses these correctly as Date type during initialization — but if they appear as text, go back to the API Connector, click Initialize, and manually change the field type dropdown from 'Text' to 'Date'.
Pro tip: RapidDev's team has built developer portals and internal tooling on Bubble with GitHub API integrations — for complex use cases like multi-repo dashboards or OAuth-based GitHub connections, book a free scoping call at rapidevelopers.com/contact.
Expected result: The Repeating Group displays GitHub issues from your repository with title, number, state, and date. Next/Previous pagination buttons load additional pages of results.
Wire up write actions — create issues and trigger GitHub Actions
With data display working, now add write capabilities. Creating an issue and triggering a workflow dispatch are the two most common write actions in developer portal use cases. For creating a GitHub issue from a Bubble form: add an API call named 'Create Issue', type POST, URL = `https://api.github.com/repos/<owner>/<repo>/issues`, body type = JSON body: ```json { "title": "<issue_title>", "body": "<issue_body>", "labels": ["<label_name>"] } ``` Set 'Use as' to 'Action'. Initialize with test values. In your Bubble editor, add a form with an Input for title and a Multiline Input for body. Add a 'Submit' button. In the Workflow for Submit click, add step: Plugins → 'GitHub - Create Issue' → map title = Input Title's value, body = Input Body's value, label = hardcoded label string like 'bug' or 'feature-request'. For triggering a GitHub Actions workflow: add an API call named 'Dispatch Workflow', type POST, URL = `https://api.github.com/repos/<owner>/<repo>/actions/workflows/<workflow_filename>/dispatches`. The workflow_filename is the YAML filename in your .github/workflows/ directory, for example 'deploy.yml'. Body: ```json { "ref": "<branch_name>" } ``` The ref is the branch name to run the workflow on (e.g., 'main'). Your GitHub Actions YAML must include a `workflow_dispatch:` trigger for this to work. A successful dispatch returns 204 No Content — no body. In Bubble, show a success popup after the action runs, using the Workflow condition 'this step has no errors'. For all write actions, go to Data tab → Privacy and confirm Privacy Rules are set on any Bubble data types that store GitHub response data — restrict read access to logged-in users with the appropriate role.
1{2 "method": "POST",3 "url": "https://api.github.com/repos/<owner>/<repo>/actions/workflows/<workflow_id>/dispatches",4 "headers": {5 "Authorization": "token <github_pat - Private>",6 "Accept": "application/vnd.github.v3+json",7 "Content-Type": "application/json"8 },9 "body": {10 "ref": "<branch_name>"11 }12}Pro tip: The workflow_dispatch trigger in your GitHub Actions YAML must be set up before you can dispatch it via the API. Add 'on: workflow_dispatch:' to your YAML file and push it to GitHub before testing this endpoint from Bubble.
Expected result: Submitting the Bubble form creates a new GitHub issue in your repository. Clicking the deploy button triggers your GitHub Actions workflow. Both are verifiable in your GitHub repository within seconds.
Implement error handling and WU-aware design
GitHub API calls consume Bubble Workload Units (WU) — Bubble's post-2023 billing unit for server-side operations. Each API Connector call consumes WU proportional to the call complexity and response size. For a developer portal that displays data on page load and handles user write actions, WU cost is moderate — but if you build a polling mechanism (repeatedly checking GitHub for new issues every few seconds), WU costs can accumulate quickly. Better patterns for staying WU-efficient: 1. Use page-level caching: instead of calling GitHub on every user page load, store recently fetched data in a Bubble database table (Issues data type, last_synced timestamp). Refresh from GitHub only when last_synced is older than 5-10 minutes. This cuts API calls dramatically for apps with multiple concurrent users viewing the same repo data. 2. Avoid live GitHub polling: if you need real-time issue updates, consider using GitHub webhooks to push changes to a Bubble Backend Workflow endpoint (paid plan required) rather than polling the API on an interval. 3. Add Workflow error handling: in each Workflow that calls GitHub, add a second step that only runs 'when step 1 API call returned an error'. Show the user a clear error message: 'GitHub request failed — please try again in a moment.' Check Bubble's Logs tab (left sidebar → Logs) to see the specific HTTP status code returned. Common error scenarios: - 401 Unauthorized: PAT expired or revoked — regenerate and update the API Connector header - 403 Forbidden: PAT lacks the required scope, or the repo is private and the PAT does not have repo access - 404 Not Found: the owner/repo values in your URL are wrong or the repo was renamed - 422 Unprocessable Entity: the issue body or create request is missing required fields - 429 Too Many Requests: rate limit hit (5000 requests/hour for authenticated PATs) — reduce polling frequency Verify the data type Privacy Rules in Data tab → Privacy — any Bubble data type storing GitHub issue or repo data should have rules that restrict access to authenticated users with appropriate permissions.
Pro tip: Check Bubble's Logs tab after running your first GitHub API calls to confirm the WU usage per call. A simple GET /issues call typically costs 5-15 WU depending on response size — this helps you estimate WU budget for production traffic.
Expected result: Error states display user-friendly messages instead of raw API errors. Data is cached appropriately to minimize WU consumption. Privacy Rules are set on GitHub data types to restrict field access to appropriate users.
Common use cases
Build an internal issue tracker dashboard in Bubble
Display all open GitHub issues from your repo in a Bubble Repeating Group, filterable by label, assignee, or milestone. Let internal team members add comments or close issues directly from the Bubble dashboard without needing GitHub access, by routing Bubble Workflow button clicks to the GitHub API's PATCH /issues/{issue_number} endpoint.
On page load, call GET /repos/yourorg/yourrepo/issues?state=open&per_page=30 and display each issue in a Repeating Group showing the issue number, title, author login, labels list, and created_at date formatted as 'MMM D, YYYY'.
Copy this prompt to try it in Bubble
Trigger a GitHub Actions deployment from a Bubble button
Add a 'Deploy to Production' button to your Bubble internal dashboard that fires a POST to GitHub's workflow dispatch endpoint. When your team clicks the button, Bubble calls GitHub Actions, which runs your CI/CD pipeline — shipping a release without anyone needing to open a terminal.
When the 'Deploy to Production' button is clicked, call POST /repos/yourorg/yourrepo/actions/workflows/deploy.yml/dispatches with the JSON body {"ref": "main"} and show a success popup when the API returns 204 No Content.
Copy this prompt to try it in Bubble
Display a repo overview panel in a developer portal
Build a Bubble page that shows a developer portal for clients or internal teams — listing repos they have access to, with star counts, last push dates, and open issue counts. Use GET /user/repos or GET /orgs/{org}/repos and display the results in a Bubble Repeating Group with click-through to individual repo detail pages.
On page load, call GET /orgs/yourorg/repos?sort=updated&per_page=20 and display each repo in a card with the repo name, description, language, and open_issues_count. Make each card link to the GitHub repo URL in a new tab.
Copy this prompt to try it in Bubble
Troubleshooting
API call returns 401 Unauthorized despite entering the PAT in the header
Cause: The Authorization header value is incorrectly formatted — the 'token ' or 'Bearer ' prefix is missing a space, or the PAT was not copied completely (missing the last few characters). Alternatively, the PAT has expired.
Solution: In the API Connector's shared header, verify the Value field reads exactly 'token ghp_yourtokenhere' (with a space after 'token') or 'Bearer github_pat_yourtokenhere'. If the format looks correct, generate a new PAT in GitHub Settings → Developer settings → Personal access tokens and update the header value.
Initialize call shows 'There was an issue setting up your call' or returns an empty field list
Cause: The initialize call did not receive a valid non-empty JSON response — either the endpoint returned an error (401/403/404) or the endpoint returns a 204 No Content (like workflow dispatch, which should be set to 'Action' not 'Data').
Solution: For data endpoints (issues, repos), ensure the URL, owner, and repo values are correct and that the PAT has the required scope. Initialize with real test values — enter your actual GitHub username as 'owner' and a real repo name as 'repo'. For action endpoints that return 204 (workflow dispatch), change 'Use as' from 'Data' to 'Action' before initializing.
Date fields from GitHub issues display as raw ISO text instead of formatted dates in Bubble
Cause: During the Initialize call, Bubble detected the date field (e.g., created_at) as type 'Text' instead of type 'Date', because GitHub returns ISO 8601 date strings which Bubble sometimes misclassifies.
Solution: Go back to the API Connector → find the 'List Issues' call → click Initialize call → find the 'created_at' field in the detected fields list → click the type dropdown next to it and change from 'Text' to 'Date'. Click Save. Re-initialize if needed. Bubble will now parse the ISO date string correctly and expose 'formatted as' date options in expressions.
GitHub Actions workflow dispatch returns 404 Not Found
Cause: Either the workflow file does not exist at the path specified, the workflow YAML does not include a 'workflow_dispatch:' trigger, or the 'ref' (branch name) specified in the request body does not exist in the repository.
Solution: Verify the workflow filename in the URL matches the exact filename in your .github/workflows/ directory (including the .yml extension). Open the workflow YAML on GitHub and confirm it includes 'on: workflow_dispatch:' in the triggers section. Verify the branch name in the 'ref' field matches an existing branch in your repo.
1on:2 workflow_dispatch:34jobs:5 deploy:6 runs-on: ubuntu-latest7 steps:8 - uses: actions/checkout@v4GitHub API returns 403 when trying to access a private repository
Cause: The PAT does not have the 'repo' scope required to access private repositories. The 'public_repo' scope only allows access to public repositories.
Solution: Generate a new Classic PAT with the 'repo' scope checked, or use a Fine-grained PAT with 'Repository access: Only select repositories' and grant the specific permissions needed (Contents: Read, Issues: Read and Write, etc.). Update the Authorization header in the API Connector with the new token.
Best practices
- Use a fine-grained PAT scoped to specific repositories and minimum required permissions, rather than a Classic PAT with broad 'repo' scope — this limits risk if the token is ever exposed through a misconfigured Bubble Privacy Rule.
- Always mark the GitHub PAT as Private in the API Connector shared header — even if the PAT only has read-only scope, keeping it server-side prevents it from appearing in Bubble's Logs tab or browser network requests.
- Set Privacy Rules on any Bubble data types that store GitHub API data (issues, repos, PRs) — go to Data tab → Privacy and restrict field visibility to logged-in users with the appropriate role, not 'All users'.
- Cache frequently-read GitHub data in a Bubble database table with a last_synced timestamp instead of calling GitHub on every page load — this reduces WU consumption significantly for high-traffic apps.
- Never store a GitHub PAT with write scopes (repo, actions:write) in a Bubble app that is accessible to untrusted users — if an app flaw exposes the API Connector call, a write-scoped token could allow unauthorized commits or workflow triggers.
- For user-specific GitHub connections (where each Bubble user connects their own GitHub account), implement the GitHub OAuth App flow with a Backend Workflow handling the token exchange server-side — do not use a single shared PAT for multi-user scenarios.
- Use `?per_page=30&page=X` pagination parameters on all list endpoints (issues, repos, PRs) and drive the page value from a Bubble custom state — never attempt to fetch all results in one call, as large repos can have thousands of issues.
- Rotate PATs every 90 days by generating a new token and updating the API Connector shared header — this is a security hygiene best practice even for read-only tokens.
Alternatives
GitLab has a similar REST API to GitHub but uses a different auth header format ('PRIVATE-TOKEN: your_token' instead of 'Authorization: Bearer') and different endpoint structures for merge requests (GitLab) versus pull requests (GitHub). Use GitLab integration if your team hosts code on GitLab; use this GitHub guide if you use GitHub.
Jira is a purpose-built issue and project tracking tool with a richer API for workflows, sprints, and custom fields. Use Jira if you need advanced project management features (sprints, story points, Kanban boards); use GitHub Issues if your team prefers keeping issue tracking directly alongside the code repository.
Postman is a development companion for testing your Bubble app's own API endpoints, not a source of GitHub data. Use Postman to test your Bubble Backend Workflow endpoints before connecting them to GitHub webhooks; use this GitHub guide when you want to build UI tools that display or write GitHub data.
Frequently asked questions
Does Bubble's version control (Settings → Versions) work with Git?
No. Bubble's version control is a proprietary snapshot system internal to Bubble's platform. When you create a 'version' or 'branch' in Bubble's Settings → Versions, it snapshots the app state in Bubble's database — it does not create a Git commit, connect to any Git repository, or produce any files. There is no way to export a Bubble app to a Git repository or import a Git repository into Bubble.
Can I let individual users connect their own GitHub accounts to my Bubble app?
Yes, but it requires implementing GitHub's OAuth App flow, which is more complex than a simple PAT setup. You need to create a GitHub OAuth App, redirect users to GitHub's authorization URL, and exchange the returned code for an access token via a Bubble Backend Workflow (which requires a paid Bubble plan). The access token must be stored securely per user in the Bubble database. This is an advanced pattern — contact RapidDev if you need help implementing it.
How many GitHub API calls can I make from Bubble before hitting rate limits?
GitHub allows 5,000 requests per hour for authenticated requests using a Classic PAT. Fine-grained PATs use the same limit. If your Bubble app has many concurrent users all triggering API calls, you could approach this limit. Mitigate by caching GitHub data in Bubble's database and refreshing only when stale, rather than fetching on every page load.
Can Bubble receive GitHub webhook events (e.g., push events, issue events)?
Yes, but only on a paid Bubble plan (Starter or above). You set up a Bubble Backend Workflow as a public endpoint, enable 'This workflow can be run without authentication', and configure the Bubble endpoint URL as the webhook URL in your GitHub repository settings. Bubble will then receive push events, issue events, PR events, and more whenever they occur in GitHub.
What GitHub API scopes do I need for common developer portal use cases?
For a read-only issue dashboard: 'public_repo' (for public repos) or 'repo' (for private repos). For creating issues: 'repo' scope. For triggering GitHub Actions workflows: 'actions:write'. For listing organization repos: 'read:org'. For OAuth-based user connections: 'user' scope. Always use the minimum scopes needed for your specific use case.
Why is GitHub returning 404 for my repository even though it exists?
The most common causes are: (1) the owner or repo name in the URL has a typo or different casing than the actual GitHub URL, (2) the repository is private and your PAT does not have the 'repo' scope, or (3) the repository was renamed after you set up the API Connector call. Verify the exact owner/repo values from your GitHub repository's URL and ensure your PAT has the appropriate scope for private repos.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation