Connect FlutterFlow to Jenkins via a FlutterFlow API Call group using Basic Auth (username + API token). To trigger builds, you must first fetch a Jenkins-Crumb from /crumbIssuer/api/json — Jenkins rejects write requests without this CSRF token. Poll build status from /job/{name}/lastBuild/api/json. Credentials must route through a Firebase Cloud Function or Supabase Edge Function proxy — Jenkins often lives on a private network, making a server-side proxy mandatory.
| Fact | Value |
|---|---|
| Tool | Jenkins |
| Category | DevOps & Tools |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 45 minutes |
| Last updated | July 2026 |
Building a Jenkins CI Dashboard and Build Trigger in FlutterFlow
Jenkins integration with FlutterFlow serves two practical goals: reading build status (a read-only poll) and triggering builds (a write operation that modifies Jenkins state). Both paths use Jenkins' JSON REST API, but they differ in complexity. Reading build status is a straightforward GET call with Basic Auth. Triggering a build requires a two-request sequence that most developers miss the first time: you must first fetch a CSRF crumb from Jenkins and include it in the trigger request's headers, or Jenkins returns a 403 error with 'No valid crumb' in the body.
Jenkins is free and open-source — it is self-hosted on your own infrastructure, so there is no subscription fee beyond the server costs. This also means your Jenkins instance lives on a private network or VPN. A FlutterFlow app running on a user's phone cannot reach a server that is not on the public internet — making the backend proxy not just a security requirement but a network requirement. The proxy (a Firebase Cloud Function or Supabase Edge Function) sits in the public cloud, authenticates with Jenkins using the stored credentials, and forwards requests from your FlutterFlow app.
Jenkins has no published rate limit since it is self-hosted — your instance's performance is the only constraint. The Jenkins REST API uses Basic Auth: your Jenkins username and an API token (not your Jenkins login password). API tokens are generated per-user in Jenkins > People > your user profile > Configure > API Token > Generate.
Integration method
Jenkins exposes a REST API at your server's base URL. FlutterFlow calls this API through a Firebase Cloud Function or Supabase Edge Function proxy that holds Jenkins credentials server-side. The proxy handles two distinct flows: polling build status (GET requests to /job/{name}/lastBuild/api/json) and triggering builds (a two-step flow — GET /crumbIssuer/api/json to fetch the CSRF crumb, then POST /job/{name}/build or /buildWithParameters with the Jenkins-Crumb header). FlutterFlow orchestrates the two-step trigger flow using an Action Flow Editor sequence.
Prerequisites
- A FlutterFlow project open in your browser
- A running Jenkins instance accessible by your backend proxy (either public HTTPS or VPN-accessible)
- A Jenkins user account with sufficient permissions to read jobs and trigger builds
- A Firebase project (Cloud Functions enabled) or a Supabase project for the proxy backend
- Your Jenkins username and an API token generated from your Jenkins user profile
Step-by-step guide
Generate a Jenkins API token from your user profile
Jenkins API authentication uses your Jenkins username paired with a user-specific API token — not your Jenkins login password. The API token is separate from your password and can be revoked independently without affecting your ability to log into Jenkins. To generate one, log into your Jenkins web UI and navigate to People > your username > Configure (left sidebar). Scroll down to the 'API Token' section. Click 'Generate'. Jenkins shows you the token value once — copy it immediately and save it in a password manager. The token will not be displayed again after you leave the page. If you lose it, you can generate a new one from the same location. The API token is used in HTTP Basic Auth. Jenkins Basic Auth encodes the username and API token as 'username:apiToken' in base64, then passes it in the Authorization header as 'Basic {base64string}'. Some Jenkins configurations also accept the API token as a Bearer token — check your Jenkins version's documentation, but Basic Auth is universally supported. Important: the API token must have at least 'Read' permissions on jobs you want to poll, and 'Build' permissions on jobs you want to trigger. These permissions are controlled by your Jenkins security realm (Matrix Authorization, Role-Based, etc.) — ask your Jenkins admin if you get 403 responses with the correct token. Do NOT use this API token in FlutterFlow's API Call headers or any Dart code — it must stay in the backend proxy's environment variables.
Pro tip: Jenkins API tokens can have expiration dates set (Jenkins 2.x feature). If your integration suddenly stops working with 401 errors, check whether the token has expired in Jenkins > People > your user > Configure > API Token.
Expected result: You have a Jenkins API token copied and stored securely. You know your Jenkins username. Together, these form the Basic Auth credentials your proxy will use to authenticate with Jenkins.
Understand the CSRF crumb — why Jenkins rejects builds without it
Jenkins implements Cross-Site Request Forgery (CSRF) protection for all write operations (triggering builds, stopping builds, modifying configurations). This protection requires that any HTTP POST request includes a 'Jenkins-Crumb' header with a value freshly fetched from /crumbIssuer/api/json. If you send a POST to trigger a build without this crumb — or with a stale crumb — Jenkins returns a 403 HTTP response with the message 'No valid crumb was included in the request'. The crumb is fetched with a GET request to https://{your-jenkins-host}/crumbIssuer/api/json using the same Basic Auth credentials. The response is a JSON object with two fields: 'crumbRequestField' (the name of the header to use — usually 'Jenkins-Crumb') and 'crumb' (the value for that header). You then include this header in your subsequent POST request. Crumbs are session-tied: the crumb is linked to the HTTP session established by the GET /crumbIssuer request. If your proxy fetches a fresh crumb and immediately uses it in the same proxy function, the session is consistent and the crumb is valid. Problems arise when the crumb is cached across requests (the session expires) or when the proxy changes the session between the two calls. In FlutterFlow, you cannot chain two API Calls automatically in a Backend Query — you need to use an Action Flow: call the GetCrumb API Call first, store the returned crumb value in a page state variable, then call the TriggerBuild API Call passing the crumb as a variable. The next step shows this Action Flow setup.
Pro tip: Some Jenkins configurations have CSRF protection disabled (usually older Jenkins versions or specially configured instances). If /crumbIssuer/api/json returns a 404, CSRF is disabled and you can POST directly without a crumb header. However, do not rely on this — add crumb handling anyway for portability.
Expected result: You understand the crumb requirement: every build trigger POST needs a fresh crumb fetched from /crumbIssuer/api/json immediately before the trigger. Your proxy handles both requests in sequence.
Deploy a Firebase Cloud Function proxy for Jenkins
Because Jenkins typically lives on a private network (or a VPN-protected server), your FlutterFlow app cannot reach it directly. Even if Jenkins were on a public IP, putting the API token in a FlutterFlow API Call header would ship it in the compiled app binary. The Firebase Cloud Function proxy solves both problems: it sits in the public cloud, holds the Jenkins token in an environment variable, and forwards requests to your Jenkins server. In the Firebase Console, navigate to Functions. If you have not initialized Cloud Functions for your project, do so via the Firebase Console's guided setup. Create a new function (or use the inline editor for simple functions). The function below handles both read (get build status) and write (trigger build) operations in a single endpoint, determined by the 'action' query parameter. Set your Jenkins credentials as Firebase environment variables: JENKINS_URL (the full base URL including protocol, e.g., https://jenkins.yourcompany.com), JENKINS_USER (your username), and JENKINS_TOKEN (the API token). Set these in Firebase Console > Functions > your function > Runtime environment variables, or using Firebase CLI. The function reads them with process.env. For the two-step crumb + trigger flow, the function first calls /crumbIssuer/api/json, extracts the crumb, then immediately calls /job/{name}/build or /buildWithParameters — all within the same server-side request. This ensures the crumb is always fresh and from the correct session.
1// Firebase Cloud Function — Jenkins proxy2// Environment variables: JENKINS_URL, JENKINS_USER, JENKINS_TOKEN34const functions = require('firebase-functions');56const makeAuth = () => {7 const creds = Buffer.from(8 `${process.env.JENKINS_USER}:${process.env.JENKINS_TOKEN}`9 ).toString('base64');10 return `Basic ${creds}`;11};1213exports.jenkinsProxy = functions.https.onRequest(async (req, res) => {14 res.set('Access-Control-Allow-Origin', '*');15 res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');16 res.set('Access-Control-Allow-Headers', 'Content-Type');17 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }1819 const base = process.env.JENKINS_URL;20 const auth = makeAuth();21 const { action, job, params } = req.query;2223 try {24 if (action === 'status') {25 // Get last build status26 const r = await fetch(`${base}/job/${job}/lastBuild/api/json`, {27 headers: { Authorization: auth }28 });29 const data = await r.json();30 res.json({ result: data.result, building: data.building,31 number: data.number, duration: data.duration });3233 } else if (action === 'trigger') {34 // Step 1: fetch CSRF crumb35 const crumbResp = await fetch(`${base}/crumbIssuer/api/json`, {36 headers: { Authorization: auth }37 });38 const { crumbRequestField, crumb } = await crumbResp.json();3940 // Step 2: trigger build41 const endpoint = params42 ? `/job/${job}/buildWithParameters?${params}`43 : `/job/${job}/build`;44 const buildResp = await fetch(`${base}${endpoint}`, {45 method: 'POST',46 headers: { Authorization: auth, [crumbRequestField]: crumb }47 });48 const location = buildResp.headers.get('Location');49 res.json({ triggered: buildResp.status === 201, queueUrl: location });5051 } else {52 res.status(400).json({ error: 'Unknown action' });53 }54 } catch (err) {55 res.status(500).json({ error: err.message });56 }57});Pro tip: When Jenkins accepts a build trigger, it responds with HTTP 201 (not 200) and a Location header containing the queue item URL. Capture this URL if you want to poll the queue item to find out when the queued build starts running and what build number it receives.
Expected result: Your Firebase Cloud Function is deployed. Calling it with ?action=status&job=my-pipeline returns the last build result. Calling it with ?action=trigger&job=my-pipeline returns {triggered: true, queueUrl: '...'} without a '403 No valid crumb' error.
Create the FlutterFlow API Calls group and test build status polling
With the proxy running, open FlutterFlow and click API Calls in the left nav. Click + Add > Create API Group. Name it 'Jenkins CI'. Set the Base URL to your Firebase Cloud Function URL (e.g., https://us-central1-{project}.cloudfunctions.net/jenkinsProxy). Add the header 'Content-Type: application/json'. No Authorization header goes here — the proxy handles Jenkins auth entirely. Create two API Calls inside the group: First call — 'GetBuildStatus': Method = GET. Add a Variable named 'job' (String). Set the endpoint path to '' (empty — your proxy is the endpoint), and add a Query Parameter 'action' with value 'status' and a Query Parameter 'job' with value '{{job}}'. Switch to the Response & Test tab, enter a real Jenkins job name in the 'job' input, and click Test. If the proxy is running correctly, you see a JSON response like: { "result": "SUCCESS", "building": false, "number": 42, "duration": 94321 }. Click Generate JSON Paths to extract $.result, $.building, $.number, and $.duration. Second call — 'TriggerBuild': Method = GET (the proxy converts it to POST internally). Add Variables: 'job' (String) and 'params' (String, optional for parameterized builds). Set Query Parameters: 'action' = 'trigger', 'job' = '{{job}}', 'params' = '{{params}}'. Test with a safe test job. The response should be { "triggered": true, "queueUrl": "..." }. With both calls configured and tested in the Test tab, move on to binding the status call to a widget. Add a Backend Query (not a Live Query, since you control polling timing) on a page that calls GetBuildStatus with the selected job name. Bind $.result to a Text widget and $.building to a Boolean App State variable that drives a loading indicator.
1// FlutterFlow API Call configuration reference2{3 "group_name": "Jenkins CI",4 "base_url": "https://us-central1-{project}.cloudfunctions.net/jenkinsProxy",5 "headers": { "Content-Type": "application/json" },6 "calls": [7 {8 "name": "GetBuildStatus",9 "method": "GET",10 "query_params": { "action": "status", "job": "{{job}}" },11 "json_paths": {12 "result": "$.result",13 "building": "$.building",14 "buildNumber": "$.number",15 "duration": "$.duration"16 }17 },18 {19 "name": "TriggerBuild",20 "method": "GET",21 "query_params": { "action": "trigger", "job": "{{job}}", "params": "{{params}}" },22 "json_paths": {23 "triggered": "$.triggered",24 "queueUrl": "$.queueUrl"25 }26 }27 ]28}Pro tip: Test every API Call in FlutterFlow's Response & Test tab with a real Jenkins job name before building widgets. The test tab runs from FlutterFlow's servers so it will reach your Cloud Function regardless of CORS — which means a passing test does not guarantee the web preview will work without CORS headers in the proxy.
Expected result: The Jenkins CI API Call group shows GetBuildStatus and TriggerBuild. The GetBuildStatus test returns the real last-build result from Jenkins through the proxy. JSON paths are generated and ready to bind to widgets.
Wire the build trigger in an Action Flow with confirmation dialog
Triggering a Jenkins build is a destructive action — it consumes build minutes and can overwrite a running build. Wire it behind a confirmation dialog in FlutterFlow to prevent accidental taps. Add a Button widget to your page (e.g., 'Trigger Build'). Open the widget's Actions panel and click + Add Action. Choose 'Alert Dialog' as the first action — set the title to 'Trigger Build?' and message to 'This will start a new Jenkins build for the selected job.' with Confirm and Cancel buttons. After the Alert Dialog action (in the 'True' branch, meaning the user clicked Confirm), add an API Request action using TriggerBuild. Pass the page variable holding the selected job name as the 'job' variable. If building a parameterized trigger, pass the branch or environment value as the 'params' variable (formatted as URL query params, e.g., 'BRANCH=main&ENV=staging'). After the TriggerBuild action, add a Show Snack Bar action with the message 'Build triggered successfully!' (conditionally shown only when the response $.triggered field is true). Add another branch for failure ($.triggered is false or the call returned an error) showing an error snack bar. For parameterized builds: Jenkins uses /buildWithParameters rather than /build for jobs with defined parameters. Your proxy handles this by appending the 'params' query string to the correct endpoint. Using /build for a parameterized job silently ignores the parameters — this is a common gotcha that causes builds to run with default values instead of the intended ones. Always check your Jenkins job config to confirm whether it is parameterized. If you want help setting up a more sophisticated Jenkins dashboard with real-time status updates, RapidDev's team builds FlutterFlow backend integrations regularly — free scoping call at rapidevelopers.com/contact.
Pro tip: FlutterFlow's Action Flow Editor processes actions sequentially. The Alert Dialog action blocks until the user taps Confirm or Cancel. Only the actions in the 'True' branch (Confirm) execute if the user confirms. This sequential flow makes it easy to add confirmation dialogs before any destructive API call.
Expected result: Tapping 'Trigger Build' shows a confirmation dialog. On Confirm, the TriggerBuild API Call fires through the proxy, which fetches a fresh CSRF crumb and posts to Jenkins. A success snack bar confirms the build is queued. No '403 No valid crumb' error appears.
Display Jenkins build status with auto-refresh in FlutterFlow widgets
To create a live status dashboard, you need the build status to update without the user manually refreshing. FlutterFlow offers a few approaches for auto-refreshing API data. Add a ListView to the page for displaying multiple Jenkins jobs. For each job, add a Card containing: a Text widget for the job name, a Text widget bound to $.result (with conditional color: green for SUCCESS, red for FAILURE, orange for UNSTABLE, gray for building or null), a Text widget for the build number (bound to $.number), and a CircularProgressIndicator that shows when $.building is true. For auto-refresh, use FlutterFlow's Timer action: in the page's initState actions (click the page name, go to the Actions tab, add actions for 'On Page Load'), add a Timer action set to repeat every 30 seconds. In the timer's callback, call the GetBuildStatus API Call and update the page state variable holding the status values. This creates a polling dashboard without requiring real-time server infrastructure. For console output (showing error text from a failed build), add a third API Call to the Jenkins CI group: 'GetConsoleOutput', method GET, query params action=console and job={{job}}. Update your proxy to handle action=console by fetching https://{base}/job/{job}/lastBuild/consoleText (Jenkins returns plain text, not JSON). In FlutterFlow, display the response body in a Text widget with a monospace font inside a scrollable Container. Note: Jenkins console output can be large (thousands of lines). In your proxy, slice the last 100 lines before returning to keep the response manageable for a mobile UI.
Pro tip: Use FlutterFlow's Conditional Widget Visibility to show a 'Build In Progress' banner (with a progress animation) when $.building is true, and collapse it when false. This gives users immediate visual feedback that a build is running without requiring them to read the status text.
Expected result: The FlutterFlow app shows Jenkins job names with color-coded build results that refresh every 30 seconds. A CircularProgressIndicator appears next to any job currently building. Tapping a job card shows the last build's console output summary.
Common use cases
Mobile CI dashboard showing Jenkins build status per project
A development team builds a FlutterFlow app that shows the current build status (passing, failing, in progress) for each Jenkins job in their organization. Team members open the app on their phones to check build health without logging into Jenkins. The app polls the Jenkins API every 30 seconds via a backend proxy and refreshes the status cards automatically.
Build a CI status dashboard app that shows all Jenkins jobs from our server, displaying each job's name, last build result (SUCCESS/FAILURE/UNSTABLE), build number, and duration — in a color-coded ListView that refreshes every 30 seconds.
Copy this prompt to try it in FlutterFlow
Build trigger app for non-technical team members
A QA team needs to trigger Jenkins regression test builds at specific times but does not have Jenkins access. A FlutterFlow app provides a simple UI: select a job from a dropdown, enter optional parameters (e.g., branch name, environment), and tap 'Trigger Build'. The app handles the two-step crumb-fetch-then-POST flow transparently so the QA team never touches Jenkins directly.
Build a build trigger app with a dropdown to select a Jenkins job, an input field for the branch name parameter, and a Trigger Build button. Show the build number and status after triggering, with a success/failure toast notification.
Copy this prompt to try it in FlutterFlow
DevOps ops app combining Jenkins status and deployment controls
An internal operations FlutterFlow app that combines Jenkins build status with one-click deployment triggers. Engineers see the current status of build, test, and deploy pipelines on a single screen. They can trigger a deploy pipeline for a specific environment (staging, production) with confirmation dialogs, all routed through a secure backend proxy that validates permissions before forwarding to Jenkins.
Build an ops dashboard with three tabs: Build (shows last 5 builds per job), Deploy (trigger buttons for staging and prod environments with confirmation dialog), and Logs (shows the console output summary of the last build).
Copy this prompt to try it in FlutterFlow
Troubleshooting
403 error: 'No valid crumb was included in the request'
Cause: The Jenkins CSRF protection rejected the build trigger because the Jenkins-Crumb header was missing, stale, or from a different session than the one that generated it.
Solution: Ensure your proxy fetches the crumb from /crumbIssuer/api/json using the same authentication credentials immediately before the POST request, and passes the crumb value in the same proxy function call. Never cache the crumb across separate proxy calls — fetch a fresh one every time a build is triggered.
Jenkins build triggered but ran with default parameters, ignoring the branch or env values
Cause: The POST was sent to /job/{name}/build instead of /job/{name}/buildWithParameters for a parameterized job. Jenkins silently ignores parameters sent to the wrong endpoint.
Solution: Check whether the Jenkins job has parameters defined (open the job in Jenkins > Configure — if you see 'This project is parameterized', it requires /buildWithParameters). Update your proxy to use /buildWithParameters and pass parameters as a URL query string (e.g., ?BRANCH=main&ENV=staging) appended to the endpoint.
XMLHttpRequest error when calling the Jenkins proxy from FlutterFlow's web preview
Cause: The Firebase Cloud Function is missing CORS headers, or the FlutterFlow web app's origin is blocked. Without CORS headers, browsers block cross-origin requests.
Solution: Add Access-Control-Allow-Origin: * and Access-Control-Allow-Methods: GET, POST, OPTIONS headers to every response from your Cloud Function, including the OPTIONS preflight response. See the proxy code in Step 3 — the CORS headers must be set before any await call to avoid a situation where an error occurs before headers are set.
Proxy returns 500 or timeout — cannot reach Jenkins
Cause: Your Jenkins server is on a private network (VPN, internal firewall) and the Firebase Cloud Function cannot reach it. This is a network issue, not an auth issue.
Solution: Ensure Jenkins is accessible from the public internet on an HTTPS endpoint, OR deploy the proxy function inside the same VPC/network as Jenkins using a VPC connector (Firebase Cloud Functions support VPC connectors for accessing private networks). An alternative: use Nginx or a public reverse proxy that tunnels to the private Jenkins instance.
Best practices
- Always fetch a fresh CSRF crumb immediately before every build trigger POST — never cache the crumb between requests, as crumbs are session-tied and expire.
- Use /buildWithParameters for parameterized Jenkins jobs — /build silently ignores parameters and causes builds to run with defaults, which is a hard-to-diagnose bug.
- Store Jenkins credentials (JENKINS_URL, JENKINS_USER, JENKINS_TOKEN) as Firebase Cloud Function environment variables — never in FlutterFlow's App State, App Values, or API Call headers.
- Add a confirmation dialog before any build trigger action in FlutterFlow — accidental taps on a mobile touchscreen can queue unwanted builds that consume CI resources.
- Test all API Calls in FlutterFlow's Response & Test tab before building widgets — the Test tab calls your proxy from FlutterFlow's servers, which confirms proxy reachability before browser CORS comes into play.
- Use a 30-second polling interval for build status rather than real-time WebSockets — Jenkins does not expose a WebSocket API, and 30-second polling is sufficient for most team dashboards.
- Slice console output to the last 100–200 lines in your proxy before returning it to FlutterFlow — full Jenkins console logs can exceed 1MB and slow down the mobile UI significantly.
Alternatives
GitLab CI/CD is cloud-hosted with a simpler REST API (no CSRF crumb required for triggers) and offers a better free tier than self-hosting Jenkins — consider it if you are setting up CI fresh.
Bitbucket Pipelines is Atlassian's cloud-native CI that pairs naturally with Bitbucket repos and uses the same Bitbucket REST API — simpler setup than Jenkins for teams already on Atlassian tools.
Docker Hub's API lets FlutterFlow read image build status and trigger builds for containerized apps — a modern complement to or replacement for Jenkins in cloud-native pipelines.
Frequently asked questions
Why does Jenkins return 403 'No valid crumb' when I trigger a build from FlutterFlow?
Jenkins requires every POST request (including build triggers) to include a Jenkins-Crumb header fetched from /crumbIssuer/api/json. This is CSRF protection. If you send a POST without fetching the crumb first, or if the crumb is from a different session, Jenkins rejects the request with 403. The fix is a two-step proxy flow: GET /crumbIssuer/api/json (same credentials, same session), extract the crumb, then immediately POST the build trigger with the crumb in the headers — all within one proxy function call.
Can I put my Jenkins API token directly in FlutterFlow's API Call headers?
No. Any value in a FlutterFlow API Call header or Dart code is compiled into the app binary and can be extracted by decompiling the APK or IPA. Jenkins API tokens must live in a Firebase Cloud Function or Supabase Edge Function environment variable — the proxy handles authentication server-side and never exposes the token to the client app.
My FlutterFlow app can reach the Jenkins proxy in the Test tab but not in the web preview — why?
FlutterFlow's API Call Test tab runs from FlutterFlow's own servers, where CORS does not apply. In the web preview (running in your browser), the browser enforces CORS and rejects responses from origins that do not send permissive CORS headers. Add Access-Control-Allow-Origin: * to all responses from your Firebase Cloud Function proxy — including the OPTIONS preflight response — to fix this.
Jenkins is on my company's internal network. Can FlutterFlow reach it?
Not directly. A FlutterFlow app on a user's phone cannot connect to a server that is only accessible on a private VPN or internal network. Your Firebase Cloud Function proxy must be able to reach the Jenkins server — either because Jenkins is on a public HTTPS endpoint, or because the Cloud Function is deployed with a VPC connector that connects it to the same private network as Jenkins.
How do I trigger a Jenkins job with parameters (e.g., branch name) from FlutterFlow?
Use /buildWithParameters instead of /build in your proxy. Parameters are passed as URL query parameters: /job/{name}/buildWithParameters?BRANCH=main&ENV=staging. In FlutterFlow, collect the parameter values in input fields or dropdowns, pass them as variables to the TriggerBuild API Call, and let the proxy append them to the Jenkins URL. Using /build for parameterized jobs silently ignores all parameter values.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation