# JFrog Artifactory

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to JFrog Artifactory by creating an API Group at your Artifactory base URL (must end in /artifactory) with an X-JFrog-Art-Api header for API keys or an Authorization Bearer header for access tokens. The standout capability is AQL search — posted to /api/search/aql as plain text with Content-Type: text/plain, not JSON, which is the single most common FlutterFlow-specific mistake.

## Building a Mobile Artifactory DevOps Dashboard with FlutterFlow

JFrog Artifactory is where your organization's compiled artifacts, Docker images, npm packages, Maven JARs, and other binary outputs live. It is not app data — Artifactory is part of your build and release pipeline, not your application's database. With that context, the honest FlutterFlow use-case is a lightweight mobile DevOps dashboard: a phone-friendly view for on-call engineers to check repository storage usage, search for artifact versions, and verify which builds have been promoted to production — all from their phones without opening a browser or a VPN-connected laptop.

FlutterFlow consumes Artifactory's REST API as an API Group, and two paths serve most use-cases: standard REST calls for repository metadata (`GET /api/repositories`, `GET /api/storage/{repo}`) and **AQL (Artifactory Query Language)** for multi-condition artifact search. AQL is Artifactory's proprietary query language — it lets you ask questions like 'find all Docker images in the releases repository modified in the last 7 days with a specific property value.' The technical quirk that trips every FlutterFlow developer: AQL is sent as a POST with `Content-Type: text/plain` and a raw text body, not as JSON. Sending AQL as `application/json` returns a 400 'AQL parsing error' response every time.

JFrog Cloud offers a free tier with 2 GB of storage and 10 GB of monthly transfer, with Pro plans starting from approximately $150/month — verify current pricing at jfrog.com because it changes. Self-hosted Artifactory is available under separate licensing terms. Importantly, self-hosted Artifactory behind a VPN is only reachable from a FlutterFlow mobile app that's connected to the corporate network or VPN — a published web FlutterFlow app can't reach an internal Artifactory instance unless it's exposed to the internet.

## Before you start

- A JFrog Artifactory instance — either JFrog Cloud (yourcompany.jfrog.io) or a self-hosted instance reachable from the internet (or from the mobile network you'll use)
- A read-only Artifactory access token (the modern auth method, generated in JFrog Platform → Administration → User Management → Access Tokens) or an API key (older method, available in user profile settings)
- Your Artifactory base URL, which must end in '/artifactory' — for JFrog Cloud: https://yourcompany.jfrog.io/artifactory; for self-hosted: https://artifactory.yourcompany.com/artifactory
- A FlutterFlow account (any plan — API Calls work on all plans)
- Basic familiarity with FlutterFlow's API Calls panel and JSON Path

## Step-by-step guide

### 1. Generate a read-only Artifactory access token

Before opening FlutterFlow, generate the credential you'll use for API calls. Artifactory supports two authentication methods:

**Access Tokens (recommended, modern path):** Log into your JFrog Platform (yourcompany.jfrog.io for Cloud) → go to Administration → User Management → Access Tokens → Generate Token. Set a descriptive name like 'FlutterFlow Dashboard Read-Only', set an expiration date, and assign a scope of 'Reader' or read-only. This generates a long-lived token you'll use with the `Authorization: Bearer` header.

**API Keys (legacy path):** In your JFrog Platform, click your profile avatar → Edit Profile → Unlock the API Key section → Generate. API keys are scoped to your user account's permissions and use the `X-JFrog-Art-Api` header. Note: JFrog is gradually deprecating API keys in favor of access tokens — prefer tokens for new setups.

Critical security note: a full-access Artifactory token or API key can delete artifacts, modify repositories, and change permissions. For a phone dashboard with read-only needs, always generate a read-only credential. A leaked read-only token allows someone to see your artifact inventory — serious but recoverable. A leaked admin token allows deleting your entire artifact storage.

Copy the generated token or API key. For tokens, you'll only see the full value once — copy it immediately.

**Expected result:** You have a read-only Artifactory access token or API key copied and ready to add as a FlutterFlow App Constant.

### 2. Create the Artifactory API Group with the correct base URL

Open FlutterFlow and click API Calls in the left navigation panel → + Add → Create API Group.

The base URL is critical: it must end in `/artifactory`. For JFrog Cloud this is `https://yourcompany.jfrog.io/artifactory`; for self-hosted it is `https://artifactory.yourcompany.com/artifactory` (or whatever hostname your instance uses). If the base URL is missing the `/artifactory` segment, every API call will return 404 — this is the second most common setup mistake.

Set the Group Name to `Artifactory`.

In the Headers section, add your auth header:

**If using an access token (recommended):**
- Header Name: `Authorization`
- Header Value: `Bearer [App Constant: ARTIFACTORY_TOKEN]`

**If using an API key (legacy):**
- Header Name: `X-JFrog-Art-Api`
- Header Value: `[App Constant: ARTIFACTORY_API_KEY]`

Note: the header name `X-JFrog-Art-Api` is case-sensitive — Artifactory is strict about the casing. Using `x-jfrog-art-api` (all lowercase) or `X-Jfrog-Art-Api` (wrong capitalization) will return a 401 even with a valid API key.

Store the credential as an App Constant: go to Settings & Integrations → App Settings → App Constants → add a constant named `ARTIFACTORY_TOKEN` (or `ARTIFACTORY_API_KEY`) and paste the value.

Also add a `Content-Type: application/json` header for standard REST calls — you'll override this for AQL calls in a separate API Call configuration.

Click Save.

```
{
  "api_group_name": "Artifactory",
  "base_url": "https://yourcompany.jfrog.io/artifactory",
  "headers": [
    {
      "name": "Authorization",
      "value": "Bearer [App Constant: ARTIFACTORY_TOKEN]"
    },
    {
      "name": "Content-Type",
      "value": "application/json"
    }
  ]
}
```

**Expected result:** The Artifactory API Group appears in the API Calls panel with the base URL ending in '/artifactory' and the correct auth header stored via App Constant.

### 3. Add a GET Repositories API Call and bind to a ListView

With the API Group configured, add your first API Call to list repositories. Inside the Artifactory API Group, click + Add API Call:

- **Call Name:** `GetRepositories`
- **Method:** GET
- **Endpoint (relative):** `api/repositories`

This calls `https://yourcompany.jfrog.io/artifactory/api/repositories` and returns a JSON array of repository objects. In the Response & Test tab, click Test. If you see a 200 response, great — paste or use the live response to generate JSON Paths. The response is a flat array of objects, so JSON Path expressions are straightforward:

- `repo_key` → `$[*].key` (repository identifier, e.g., 'docker-local')
- `repo_type` → `$[*].type` (LOCAL, REMOTE, or VIRTUAL)
- `repo_package_type` → `$[*].packageType` (Docker, Maven, npm, etc.)
- `repo_url` → `$[*].url` (for remote repositories)

In your FlutterFlow screen, add a ListView → Backend Query → API Call → select Artifactory / GetRepositories. Inside the list item, bind Text widgets to the `repo_key` and `repo_type` JSON Path values. Add a color-coded Tag widget based on `repo_type` (LOCAL = blue, REMOTE = green, VIRTUAL = orange) for quick visual identification.

For a more detailed view, add a second API Call `GetStorageInfo` with endpoint `api/storageinfo` — this returns overall storage statistics including fileCount, usedSpace, and per-repository breakdown in a `repositoriesSummaryList` array.

```
{
  "call_name": "GetRepositories",
  "method": "GET",
  "endpoint": "api/repositories",
  "json_paths": [
    { "name": "repo_key", "path": "$[*].key" },
    { "name": "repo_type", "path": "$[*].type" },
    { "name": "repo_package_type", "path": "$[*].packageType" }
  ]
}
```

**Expected result:** The GetRepositories call returns a 200 with a JSON array, and the ListView displays repository names and types correctly.

### 4. Add an AQL Search call with Content-Type: text/plain

AQL (Artifactory Query Language) is Artifactory's most powerful search capability — it lets you find artifacts by name, property, date, repository, and more in a single query. However, it has a critical non-standard requirement that breaks most FlutterFlow setups: **AQL must be sent as a POST with `Content-Type: text/plain` and a raw text body — not JSON.**

Sending an AQL query wrapped in a JSON object (`{"query": "items.find(...)"}`) returns a 400 'AQL parsing error'. The raw AQL text itself must be the entire POST body.

In FlutterFlow, this requires a separate API Call with overriding headers. Inside the Artifactory API Group, click + Add API Call:

- **Call Name:** `SearchAQL`
- **Method:** POST
- **Endpoint:** `api/search/aql`

In this API Call's Headers tab (not the Group headers), add a call-level override:
- `Content-Type` → `text/plain`

This overrides the Group-level `application/json` for this specific call only.

In the Body tab, select Raw/Text body type and add a Variable: `aql_query` (type String). Reference it as the entire body content: `[aql_query]`. In the Variables tab, add a default value for testing:

```
items.find({"repo":{"$eq":"docker-local"}}).include("name","repo","created").sort({"$desc":["created"]}).limit(20)
```

In the Response & Test tab, click Test with the default AQL query. A successful response returns a JSON object with a `results` array containing matching artifact objects. Add JSON Paths:
- `artifacts` → `$.results`
- `artifact_name` → `$.results[*].name`
- `artifact_repo` → `$.results[*].repo`
- `artifact_created` → `$.results[*].created`

Bind the results to a ListView in the same pattern as the repositories call.

```
{
  "call_name": "SearchAQL",
  "method": "POST",
  "endpoint": "api/search/aql",
  "call_level_headers": [
    {
      "name": "Content-Type",
      "value": "text/plain"
    }
  ],
  "body_type": "raw_text",
  "body": "[aql_query]",
  "variables": [
    {
      "name": "aql_query",
      "type": "String",
      "default": "items.find({\"repo\":{\"$eq\":\"docker-local\"}}).include(\"name\",\"repo\",\"created\").sort({\"$desc\":[\"created\"]}).limit(20)"
    }
  ],
  "json_paths": [
    { "name": "artifacts", "path": "$.results" },
    { "name": "artifact_name", "path": "$.results[*].name" },
    { "name": "artifact_repo", "path": "$.results[*].repo" },
    { "name": "artifact_created", "path": "$.results[*].created" }
  ]
}
```

**Expected result:** The SearchAQL call returns a 200 response with a 'results' array containing matching artifacts. An AQL body sent with Content-Type: text/plain succeeds where the same body sent as JSON would return a 400 parse error.

### 5. Handle null results defensively and test on device

The final step is making your AQL-powered screens robust to edge cases and validating on a real device.

**Defensive null handling for empty AQL results:** When an AQL query returns no matches, the response is `{"results":[],"range":{...}}` — the `results` key exists but is an empty array. However, on certain error conditions (malformed AQL, permission issues), the `results` key may be absent entirely. JSON Path expressions like `$.results[*].name` return null when applied to a missing `results` key, which causes FlutterFlow's ListView binding to throw an error.

Fix this in the FlutterFlow UI: wrap the ListView in a Conditional Widget that checks `getJsonField(response, r'$.results') != null` before rendering. When the condition is false, show an empty state message ('No artifacts found') rather than a broken list.

For the same reason, JSON Path the `results` key first with `$.results` to get the array, then use that array as the ListView source rather than binding directly to the nested paths. This gives you one null-check point rather than guarding every individual field binding.

**Test on a device for self-hosted Artifactory:** If your Artifactory instance is self-hosted and only accessible on the corporate network or VPN, FlutterFlow's web-based Test mode will reach it (since tests run from your browser, which may be on-network). But your published app's users need to be on the same network or VPN when the app runs. Test this on a physical device using the FlutterFlow mobile app or an APK build while connected to the corporate WiFi.

If you need help building a more comprehensive Artifactory dashboard or troubleshooting network connectivity, RapidDev builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.

**Expected result:** Your Artifactory dashboard handles empty search results gracefully (showing an empty state rather than crashing), and the full integration works on a physical device connected to the corporate network.

## Best practices

- Always generate a read-only access token or API key for FlutterFlow — a token that can delete artifacts must never live in a compiled Flutter binary.
- Store the Artifactory credential as a FlutterFlow App Constant (Settings & Integrations → App Settings → App Constants), not as a raw string in an API Call header or Custom Action code.
- Ensure the API Group base URL ends exactly in '/artifactory' — missing this suffix causes 404s on every call and can be difficult to diagnose.
- Use access tokens (Bearer) instead of legacy API keys (X-JFrog-Art-Api) for new integrations — JFrog is moving away from API keys and access tokens have better scope controls and expiration support.
- Always set Content-Type: text/plain at the API Call level for AQL search requests — it must override the Group-level application/json header.
- Add a .limit() clause to every AQL query to prevent unbounded result sets from overloading your UI — start with 20 and add pagination only when needed.
- Handle null results defensively in ListView bindings — wrap lists in Conditional Widgets that check for a non-null, non-empty results array before rendering.
- For self-hosted Artifactory behind a VPN, clearly communicate to users that they must be on the corporate network or VPN for the dashboard to function.

## Use cases

### On-call engineer's artifact status dashboard

A DevOps team builds a FlutterFlow app for on-call engineers to check repository storage levels, see the latest artifact versions in the releases repository, and verify that a specific build version has been deployed to the production repo. The app uses a read-only Artifactory access token and queries the REST API for repository metadata and AQL artifact searches. Engineers get phone-readable answers without VPN access to a Jenkins dashboard or a browser.

Prompt example:

```
Build a dashboard screen that fetches all Artifactory repositories from GET /api/repositories, shows each repo name and type in a ListView, and lets the engineer tap a repo to see its storage usage from GET /api/storageinfo.
```

### Release version tracker for mobile release managers

A mobile release manager uses a FlutterFlow app to track which version of the iOS and Android app builds have been promoted from the staging-local to the releases-local repository in Artifactory. An AQL search finds artifacts with specific properties (build.number, release.stage) and shows a color-coded list of recent promotions. The release manager can confirm a build is in the right repo without logging into the JFrog platform.

Prompt example:

```
Create a release tracker screen that uses AQL search to find artifacts in 'releases-local' with property 'app.platform=ios' modified in the last 30 days, and displays artifact name, version, and creation date sorted by newest first.
```

### Artifact search utility for dependency management

A large engineering organization uses a FlutterFlow app as a mobile artifact search tool. Engineers search for a package name using AQL and see matching artifacts across all repositories, including their version, repository location, and last modified date. This replaces ad-hoc browser searches in the Artifactory UI for quick lookups during incidents or code reviews.

Prompt example:

```
Add a search screen with a text input that fires an AQL query to find artifacts whose name matches the search term across all repositories, and displays results in a ListView showing artifact name, repo name, and last modified date.
```

## Troubleshooting

### API Call returns 400 with 'AQL parsing error' on the SearchAQL call

Cause: The AQL query body is being sent with Content-Type: application/json instead of Content-Type: text/plain. Artifactory's AQL endpoint requires raw plain text — it cannot parse a JSON envelope around the AQL string.

Solution: In the SearchAQL API Call (not the API Group), add a call-level Content-Type header override set to 'text/plain'. In the Body tab, use a Raw/Text body type and enter the AQL string directly as the body — do not wrap it in a JSON object or quotes. Verify in the Response & Test tab that the Content-Type header in the request preview shows 'text/plain'.

### All API Calls return 404 Not Found even though the Artifactory instance is reachable

Cause: The API Group base URL is missing the '/artifactory' path segment. Without this suffix, API Call endpoints like 'api/repositories' resolve to 'https://yourcompany.jfrog.io/api/repositories' which doesn't exist.

Solution: Edit the Artifactory API Group and add '/artifactory' to the end of the base URL. The correct format is 'https://yourcompany.jfrog.io/artifactory' for JFrog Cloud, or 'https://artifactory.yourcompany.com/artifactory' for self-hosted. Save and re-test the API Calls.

### 401 Unauthorized even with what appears to be a valid credential

Cause: Either the wrong header name is being used (X-JFrog-Art-Api for API keys vs Authorization: Bearer for access tokens), or the header name casing is incorrect. Artifactory is case-sensitive for the X-JFrog-Art-Api header.

Solution: Verify which auth method your token uses: access tokens go in 'Authorization: Bearer [token]'; legacy API keys go in 'X-JFrog-Art-Api: [key]' with that exact capitalization. Check the App Constant value has no leading/trailing spaces. If uncertain which type of credential you have, generate a new access token from JFrog Platform → Administration → User Management → Access Tokens and use the Bearer pattern.

### ListView crashes or shows no data when AQL returns an empty result set

Cause: The JSON Path expression binding to 'results[*].name' (or similar) returns null when 'results' is an empty array or missing from the response, and FlutterFlow's ListView throws an error when the bound data is null.

Solution: Wrap the ListView in a Conditional Widget that checks whether the 'results' field exists and has items before rendering the list. Bind the ListView source to the '$.results' JSON Path (which returns an empty list rather than null on empty responses), and add an empty state widget below the conditional for the 'no results' case.

### The app works in FlutterFlow's web test preview but fails on mobile with 'connection refused' or timeout

Cause: Self-hosted Artifactory is behind a corporate VPN or firewall and is not accessible from the public internet. The web preview runs in your browser (on-network), but the mobile app tries to reach the same hostname from the cellular network.

Solution: Ensure the mobile device is connected to the corporate WiFi or VPN when testing. For users who need access from external networks, ask your infrastructure team to expose Artifactory through a reverse proxy with proper authentication, or use JFrog Cloud instead of a self-hosted instance for mobile-accessible deployments.

## Frequently asked questions

### Why does my AQL query keep returning a 400 error even though the syntax looks correct?

The most common cause is sending the AQL query with Content-Type: application/json instead of Content-Type: text/plain. Artifactory's AQL endpoint at /api/search/aql expects the raw AQL string as the entire POST body in plain text format — it cannot parse a JSON object like {"query": "items.find(...)"}. In FlutterFlow, add a call-level Content-Type header override of 'text/plain' on the SearchAQL API Call, and use a Raw/Text body type with the AQL string directly as the body.

### Is it safe to store an Artifactory API key or access token in FlutterFlow?

FlutterFlow App Constants are compiled into the Flutter binary, which means a determined attacker can extract them from a distributed APK or IPA. For a read-only token scoped to browsing repository lists and artifact metadata, this is a manageable risk for internal team dashboards — someone who extracts the token can see your artifact inventory, not delete it. For any token with write or delete permissions, it must live exclusively in a server-side environment variable (Firebase Cloud Function, backend API) and never in FlutterFlow.

### Can I use FlutterFlow to upload artifacts directly to Artifactory?

You can upload files to Artifactory via its REST API (a PUT to /api/artifact-path) from a FlutterFlow Custom Action. However, the upload credentials must be secret — use a read-only token for dashboard features and route upload operations through a Firebase Cloud Function or your own backend that holds a deploy-scoped token. Never place upload credentials in a FlutterFlow App Constant, as they would be compiled into the app binary.

### Does this integration work with self-hosted Artifactory?

Yes — the same API Group and AQL patterns work with self-hosted Artifactory. The base URL changes to your internal hostname (e.g., https://artifactory.yourcompany.com/artifactory). The key constraint is network access: your FlutterFlow app users must be on the same network or VPN as the Artifactory instance. A mobile app on a cellular connection cannot reach an Artifactory instance behind a corporate firewall unless that instance is exposed via a reverse proxy or the device is on VPN.

### What does 'X-JFrog-Art-Api' mean and when should I use it instead of Bearer?

X-JFrog-Art-Api is the legacy API key authentication header for Artifactory — you place your API key directly as the header value (no 'Bearer' prefix). The modern approach is access tokens, which use the standard 'Authorization: Bearer [token]' format. For new FlutterFlow integrations, use access tokens — they support expiration dates, fine-grained scopes, and are the direction JFrog is investing in. Use X-JFrog-Art-Api only if your Artifactory admin has provided an API key and hasn't migrated to access tokens yet.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/jfrog-artifactory
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/jfrog-artifactory
