# JFrog Artifactory

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 1–2 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to JFrog Artifactory through the Bubble API Connector with an X-JFrog-Art-Api header (API key) or Authorization: Bearer (access token) — both kept Private so credentials never reach the browser. The single most common integration failure across every platform: the AQL search endpoint requires Content-Type: text/plain (not application/json). Set Bubble's Body Type to 'Raw' for the AQL call and add a text/plain Content-Type header at the call level to avoid the 400 error that catches every first-time integrator.

## Bubble + JFrog Artifactory: a DevOps artifact dashboard powered by the Artifactory REST API

JFrog Artifactory is the central binary repository in many enterprise DevOps pipelines — the place where Docker images are stored after a build, npm packages are cached and proxied, and release artifacts are versioned for deployment. It is typically accessed by DevOps engineers and release managers through the Artifactory web interface or CLI. The Bubble use-case is a read-only dashboard: on-call engineers, DevOps managers, or product teams can check which artifact versions exist, which repositories are near their storage limits, and when the last successful build artifact was pushed — all from a Bubble page, without Artifactory credentials.

The integration has three layers, in order of complexity. The first layer is repository listing: GET /api/repositories returns a JSON array of all repositories with their type, package format, and description. This is a standard JSON call that initializes and maps in Bubble with no special treatment. The second layer is storage statistics: GET /api/storageinfo returns storage usage per repository across the entire Artifactory instance. This is also JSON, but it can be slow for large deployments with thousands of repositories and gigabytes of data — cache it in Bubble's database via a Scheduled Backend Workflow rather than fetching it live on every dashboard page load.

The third layer is AQL search — the most powerful and the most commonly broken. AQL is Artifactory's custom query language for searching artifacts by any combination of metadata: repository, path, name, size, modification date, download count, or custom properties. An AQL query looks like: items.find({"repo": {"$eq": "docker-local"}, "type": {"$eq": "folder"}}).include("name","path","size","modified"). This query text is POSTed to /api/search/aql with a body of type text/plain — not JSON. Every other API endpoint on Artifactory expects JSON; this one endpoint specifically requires raw text. Sending AQL as a JSON string (even a correctly escaped one) causes a 400 error. Bubble's Body Type must be set to 'Raw' and a Content-Type: text/plain header added at the call level. Once this configuration is correct, AQL unlocks an extremely flexible artifact search capability for the Bubble dashboard.

For JFrog Cloud (yourcompany.jfrog.io), the integration works without any network configuration — the API is publicly accessible. For self-hosted Artifactory on a private network, the same public accessibility requirement as Jenkins applies.

## Before you start

- A JFrog Artifactory account — either JFrog Cloud (yourcompany.jfrog.io, free tier: 2 GB storage, 10 GB transfer/month) or a self-hosted Artifactory instance
- For self-hosted Artifactory: the REST API must be publicly accessible (network requirement same as Jenkins — Bubble Cloud cannot reach private-network servers)
- A JFrog Artifactory API key or Access Token — generated from user settings in the Artifactory web interface
- A Bubble app on any plan — read-only Artifactory queries work on the free plan; Scheduled Backend Workflows for data caching require the Starter plan or above
- The free API Connector plugin by Bubble installed in your app (Plugins → Add plugins → search 'API Connector')
- Your Artifactory base URL confirmed — for JFrog Cloud: https://yourcompany.jfrog.io/artifactory (note the required /artifactory path segment)

## Step-by-step guide

### 1. Step 1 — Generate an API key or Access Token in JFrog Artifactory

Log in to your JFrog Artifactory instance (for JFrog Cloud: yourcompany.jfrog.io). Click your username in the top-right corner and select 'Edit Profile' or 'User Management.' To generate an API key: scroll to the 'Authentication Settings' section → click 'Generate API Key' (or 'Regenerate API Key' if one already exists) → copy the key immediately. The API key does not expire unless regenerated or revoked, and it is tied to your user account. For more granular, scoped authentication: use 'Access Tokens' instead. Navigate to Administration → Identity and Access → Access Tokens → Generate Token. Set a name (e.g., 'Bubble Integration'), scope (select the repositories you want to access), and optionally an expiry time. Copy the token immediately — access tokens are displayed only once at generation time and cannot be retrieved later. Choose between the two auth methods: API key authentication uses the X-JFrog-Art-Api header (simpler, suitable for most use-cases); access token uses Authorization: Bearer (preferred for scoped, time-limited access). For a read-only Bubble dashboard, the API key method is simpler. For production use with security requirements, access tokens with a 90-day expiry and read-only scope are recommended.

```
// Auth method 1: API Key
// Location: Artifactory → Profile → Authentication Settings → Generate API Key
// Header: X-JFrog-Art-Api: YOUR_API_KEY
// Note: API key is persistent until regenerated/revoked

// Auth method 2: Access Token
// Location: Administration → Identity and Access → Access Tokens → Generate Token
// Header: Authorization: Bearer YOUR_ACCESS_TOKEN
// Note: Displayed only ONCE — store immediately
// Set scope to: 'applied-permissions/user' or specific repo permissions
// Set expiry: 90 days recommended for production

// JFrog Cloud base URL format (note the required /artifactory segment):
// https://yourcompany.jfrog.io/artifactory

// Self-hosted base URL format:
// https://artifactory.internal.company.com/artifactory
// (must include /artifactory path segment)

// COMMON MISTAKE: Omitting /artifactory from the base URL
// Without it, all API endpoints return 404
// Wrong:  https://yourcompany.jfrog.io
// Right:  https://yourcompany.jfrog.io/artifactory
```

**Expected result:** You have either an API key or an access token copied and stored. You have noted your Artifactory base URL including the /artifactory path segment. You know which auth header format to use (X-JFrog-Art-Api or Authorization: Bearer).

### 2. Step 2 — Configure the Bubble API Connector group with Artifactory credentials

Open your Bubble app editor → Plugins → API Connector. If the API Connector is not installed, click 'Add plugins,' search for 'API Connector,' and install it (free). Click 'Add another API.' Name this group 'JFrog Artifactory.' In the 'Root URL' field, enter your Artifactory base URL with the /artifactory segment: for JFrog Cloud this is https://yourcompany.jfrog.io/artifactory. Do not add any path beyond /artifactory here — individual calls will append /api/repositories, /api/storageinfo, and so on. Under 'Shared headers across all calls,' add your auth header. If using API key: click 'Add a shared header,' key = 'X-JFrog-Art-Api,' value = your API key, tick 'Private' checkbox. If using access token: key = 'Authorization,' value = 'Bearer YOUR_ACCESS_TOKEN,' tick 'Private' checkbox. Add a second shared header: key = 'Content-Type,' value = 'application/json' (for standard JSON calls — this will be overridden at the call level for the AQL call). The Private checkbox on the auth header ensures the credential stays on Bubble's servers and is never exposed in browser requests. This is the foundation for all Artifactory calls.

```
// Bubble API Connector — JFrog Artifactory group configuration
{
  "api_group_name": "JFrog Artifactory",
  "root_url": "https://yourcompany.jfrog.io/artifactory",
  "shared_headers": [
    {
      "key": "X-JFrog-Art-Api",
      "value": "<private: your-api-key-here>",
      "private": true,
      "comment": "Use this for API key auth. Replace with Authorization: Bearer for access token auth."
    },
    {
      "key": "Content-Type",
      "value": "application/json",
      "comment": "Default for standard JSON calls. Overridden per-call for AQL (text/plain)."
    }
  ]
}

// Alternative if using Access Token instead of API Key:
// {
//   "key": "Authorization",
//   "value": "Bearer <private: your-access-token>",
//   "private": true
// }
```

**Expected result:** The JFrog Artifactory API Connector group exists with root URL ending in /artifactory and the auth header marked Private. A Content-Type: application/json shared header is set at the group level. No individual calls have been added yet.

### 3. Step 3 — Add the 'List Repositories' call and initialize it

Inside the JFrog Artifactory API Connector group, click 'Add call.' Name this call 'List Repositories.' Set method to GET. Endpoint: /api/repositories. Set 'Use as' to 'Data.' You can optionally add a URL parameter 'type' with value 'local' to filter to only local (non-virtual, non-remote) repositories, or leave it empty to retrieve all repository types. Click 'Initialize call.' Bubble makes a real GET request to your Artifactory instance. The response is a JSON array where each element represents a repository with fields: key (the repository name), type (LOCAL, REMOTE, VIRTUAL, FEDERATED), description, packageType (Maven, Docker, npm, pypi, etc.), and url. Bubble detects these fields after initialization. Click 'Save.' This call is the foundation of the Artifactory dashboard — it provides the list of repositories you can display, drill into with storage statistics, or search with AQL. For large Artifactory instances with hundreds of repositories, the response may be large but should be well within Bubble's parsing capacity as it is a flat array without deep nesting.

```
// Bubble API Connector — 'List Repositories' call
{
  "call_name": "List Repositories",
  "method": "GET",
  "endpoint": "/api/repositories",
  "parameters": [
    {
      "key": "type",
      "value": "local",
      "comment": "Optional filter: local | remote | virtual. Remove to get all types."
    }
  ],
  "use_as": "Data"
}

// Artifactory response (array of repository objects):
// [
//   {
//     "key": "docker-local",
//     "type": "LOCAL",
//     "description": "Local Docker repository for CI build artifacts",
//     "url": "https://company.jfrog.io/artifactory/docker-local",
//     "packageType": "Docker"
//   },
//   {
//     "key": "npm-proxy",
//     "type": "REMOTE",
//     "description": "Proxy for npmjs.org",
//     "url": "https://company.jfrog.io/artifactory/npm-proxy",
//     "packageType": "Npm"
//   }
// ]
```

**Expected result:** The 'List Repositories' call is initialized. Bubble detects the repository array with fields: key, type, description, url, packageType. The call is set to 'Use as: Data.'

### 4. Step 4 — Add the AQL search call with Body Type = Raw and text/plain Content-Type

This is the most important and most commonly misconfigured step. Click 'Add call.' Name it 'AQL Search.' Set method to POST. Endpoint: /api/search/aql. Now, before configuring the body: add a call-level header (not group-level) to override the Content-Type for this specific call. In the call's headers section, add: key = 'Content-Type,' value = 'text/plain.' This is critical — without it, Bubble sends the group-level 'application/json' Content-Type, and Artifactory returns a 400 error. Next, set the Body Type. In Bubble's API Connector body configuration, find the body type selector and choose 'Raw (text/plain).' The body field should accept free-form text. Enter a sample AQL query as the body value: items.find({"repo":{"$eq":"docker-local"}}).include("name","path","size","modified").sort({"$desc":["modified"]}).limit(25). This is valid AQL that finds the 25 most recently modified items in the docker-local repository. The body should be treated as a dynamic parameter in Bubble so you can pass different AQL queries from workflows. Set 'Use as' to 'Data.' Click 'Initialize call.' If you see a 400 error, the Content-Type or Body Type is not set correctly. If the response returns results, Bubble will detect an outer 'results' array containing artifact objects with name, path, size, modified, and other fields. Click 'Save.'

```
// Bubble API Connector — 'AQL Search' call
// THIS IS THE CRITICAL CONFIGURATION — read carefully

{
  "call_name": "AQL Search",
  "method": "POST",
  "endpoint": "/api/search/aql",

  "call_level_headers": [
    {
      "key": "Content-Type",
      "value": "text/plain",
      "comment": "REQUIRED: Overrides the group-level application/json header. AQL endpoint ONLY accepts text/plain."
    }
  ],

  "body_type": "Raw (text/plain)",

  "body": "items.find({\"repo\":{\"$eq\":\"docker-local\"}}).include(\"name\",\"path\",\"size\",\"modified\").sort({\"$desc\":[\"modified\"]}).limit(25)",

  "use_as": "Data"
}

// AQL query format (raw text — NOT JSON-encoded):
// items.find({"repo":{"$eq":"docker-local"}})
//   .include("name","path","size","modified","created")
//   .sort({"$desc":["modified"]})
//   .limit(25)

// Common AQL query examples:
// Find all Docker image folders in a repo:
// items.find({"repo":{"$eq":"docker-local"}, "type":{"$eq":"folder"}}).include("name","path","modified").limit(50)

// Find artifacts by file extension:
// items.find({"repo":{"$eq":"maven-releases"}, "name":{"$match":"*.jar"}}).include("name","size","modified").limit(25)

// Artifactory AQL response structure:
// {
//   "results": [
//     { "repo": "docker-local", "path": "my-image/1.2.3", "name": ".manifest", "size": 0, "modified": "2026-07-09T10:00:00.000Z" },
//     ...
//   ],
//   "range": { "start_pos": 0, "end_pos": 25, "total": 142 }
// }
```

**Expected result:** The 'AQL Search' POST call has a call-level Content-Type: text/plain header (overriding the group header) and Body Type set to Raw. Initialize call succeeds and returns results in the AQL response format. Bubble detects the 'results' array with artifact fields. The call is set to 'Use as: Data.'

### 5. Step 5 — Add the storage info call and cache results in Bubble's database

Click 'Add call.' Name it 'Get Storage Info.' Set method to GET. Endpoint: /api/storageinfo. Set 'Use as' to 'Data.' No URL parameters needed. Click 'Initialize call.' The /api/storageinfo endpoint returns a large JSON object with a 'repositoriesSummaryList' array, where each element has: repoKey (repository name), repoType (LOCAL, REMOTE, VIRTUAL), usedSpace (human-readable string like '2.63 GB'), foldersCount, filesCount, itemsCount, and percentageOfTotalSize. It also contains a 'fileStoreSummary' object with totalSpace and usedSpace for the entire Artifactory instance. Important: /api/storageinfo can be slow — it calculates storage usage across the entire Artifactory file store, which may take several seconds for large instances. Do not call this endpoint live on every page load. Instead, create a Bubble data type 'ArtifactoryStorage' with fields: repo_key (text), repo_type (text), used_space (text), files_count (number), folders_count (number), last_synced (date). Set up a Scheduled Backend Workflow to call 'Get Storage Info' daily (every 86400 seconds), upsert ArtifactoryStorage records using repo_key as the deduplication key, and serve the dashboard from these cached Bubble database records.

```
// Bubble API Connector — 'Get Storage Info' call
{
  "call_name": "Get Storage Info",
  "method": "GET",
  "endpoint": "/api/storageinfo",
  "use_as": "Data"
}

// Artifactory /api/storageinfo response structure:
// {
//   "repositoriesSummaryList": [
//     {
//       "repoKey": "docker-local",
//       "repoType": "LOCAL",
//       "foldersCount": 142,
//       "filesCount": 891,
//       "usedSpace": "2.63 GB",
//       "itemsCount": 1033,
//       "packageType": "Docker",
//       "percentageOfTotalSize": "18.2%"
//     }
//   ],
//   "fileStoreSummary": {
//     "storageType": "filesystem",
//     "storageDirectory": "/var/opt/jfrog/artifactory/data",
//     "totalSpace": "500 GB",
//     "usedSpace": "14.44 GB",
//     "freeSpace": "485.56 GB"
//   }
// }

// Bubble data type: ArtifactoryStorage
// Fields: repo_key(text), repo_type(text), used_space(text),
//         files_count(number), folders_count(number), last_synced(date)

// Scheduled Backend Workflow: 'Sync Artifactory Storage'
// Step 1: Call Artifactory > Get Storage Info
// Step 2: Schedule API Workflow on result.repositoriesSummaryList
//   Sub: upsert ArtifactoryStorage where repo_key = current item's repoKey
// Schedule: every 86400 seconds (daily)
```

**Expected result:** The 'Get Storage Info' call is initialized and returns the repositoriesSummaryList array with per-repository storage data. The Scheduled Backend Workflow caches this data in ArtifactoryStorage records daily. The Bubble dashboard serves storage data from the cached database records.

### 6. Step 6 — Build the Artifactory dashboard in Bubble

Create a Bubble page named 'artifact-dashboard' or 'devops-storage.' Add a summary section at the top with total storage used across all repositories (a Text element displaying 'Do a search for ArtifactoryStorage:sum of files_count' for total file count), and total repositories count. Add a primary Repeating Group with type ArtifactoryStorage, data source = search for ArtifactoryStorage sorted by used_space descending (note: alphabetical sort, not numeric, due to the string format). Cells display: repo_key as the repository name, a packageType badge (stored from the List Repositories call), used_space string, files_count formatted with commas, and a 'Last synced' date. Add a second section for AQL artifact search: a Text Input for the AQL query, a Dropdown for common queries (pre-loaded with options like 'Latest Docker images,' 'Recent Maven JARs'), and a 'Search' button. On Search button click, call the 'AQL Search' API Connector action with the text input value as the AQL body. Display results in a Repeating Group with artifact name, path, size, and modified date. For a drill-down experience, clicking a repository row in the first repeating group can navigate to a detail page and trigger an AQL query filtered to that specific repository. If your team is building a production artifact management dashboard in Bubble and needs architecture support, RapidDev has worked with engineering teams on similar DevOps tooling — book a free scoping call at rapidevelopers.com/contact.

```
// Bubble page: artifact-dashboard

// Summary metrics (at page top):
// Total repositories: Do a search for ArtifactoryStorage:count
// Total files:        Do a search for ArtifactoryStorage:sum of files_count
// Last storage sync:  Do a search for ArtifactoryStorage:first item's last_synced (formatted)

// Primary Repeating Group — Repository List:
// Type: ArtifactoryStorage
// Source: Do a search for ArtifactoryStorage, sorted by used_space descending
// Cells:
//   repo_key       → Text element (bold)
//   repo_type      → Badge (LOCAL=blue, REMOTE=gray, VIRTUAL=purple)
//   used_space     → Text element (right-aligned)
//   files_count    → Text element, formatted: formatted as #,###
//   last_synced    → Text element, formatted as 'Updated MMM D'

// AQL Search section:
// Text Input: placeholder 'Enter AQL query or select preset below'
// Dropdown: preset queries
//   Options: ['Latest Docker Images', 'Recent Maven JARs', 'Large Files (>100MB)']
//   Value templates:
//     'Latest Docker Images': items.find({"type":{"$eq":"folder"}}).include("name","path","modified").sort({"$desc":["modified"]}).limit(20)
//     'Recent Maven JARs': items.find({"name":{"$match":"*.jar"}}).include("name","path","size","modified").sort({"$desc":["modified"]}).limit(25)

// AQL results Repeating Group:
// Workflow: Button click → Call API connector: JFrog Artifactory > AQL Search
//   body = Text Input's value (or Dropdown's selected template)
// Type: result of AQL Search's results
// Cells: name, path, size (formatted as MB), modified date
```

**Expected result:** The Artifactory dashboard displays repositories sorted by storage usage, with files count and last sync timestamp. The AQL search panel accepts custom or preset queries and displays artifact results. Storage data loads instantly from Bubble's database rather than from a live (slow) Artifactory API call.

## Best practices

- Always include the /artifactory path segment in the Root URL — it is required for all Artifactory REST API endpoints and is the most common configuration error, causing 404 errors on every call.
- Use X-JFrog-Art-Api for API key auth and Authorization: Bearer for Access Token auth — mark whichever you choose as 'Private' in Bubble's API Connector so the credential stays server-side and never reaches user browsers.
- Configure the AQL call with both a call-level Content-Type: text/plain header AND Body Type set to 'Raw' — both settings are required; either one alone does not fix the 400 error from Artifactory.
- Cache /api/storageinfo results in a Bubble ArtifactoryStorage data type via a daily Scheduled Backend Workflow — the storage info endpoint can be slow for large instances and should never be called live on dashboard page load.
- Use numeric byte counts (available in some Artifactory API versions) rather than the human-readable 'usedSpace' strings when you need to sort repositories by storage size in Bubble — the string values ('2.63 GB', '514 MB') sort alphabetically rather than numerically.
- Store JFrog Access Tokens in a password manager immediately upon generation — they are shown only once and cannot be retrieved from JFrog after the token creation dialog is closed.
- For self-hosted Artifactory, verify network accessibility from a public network browser before configuring Bubble — Cloudflare Tunnel is the fastest path to exposing a private Artifactory API port without opening firewall rules.
- Use the 'limit' clause in every AQL query to prevent large result sets from exceeding Bubble's response parsing capacity — start with limit(25) during development and increase with pagination logic for production needs.

## Use cases

### Repository storage usage dashboard for capacity planning

A Bubble app shows each Artifactory repository's storage usage, artifact count, and last-modified date. A Scheduled Backend Workflow fetches /api/storageinfo daily and stores results in a Bubble ArtifactoryRepo data type. DevOps managers identify repositories approaching storage limits without needing Artifactory admin access.

Prompt example:

```
Every day at midnight, call Artifactory API 'Get Storage Info', for each repository in the response create or update Bubble ArtifactoryRepo record with repo_key, package_type, used_space_mb, artifact_count, last_modified; sort dashboard by used_space_mb descending
```

### Latest artifact version tracker for release managers

An AQL query finds the most recently modified artifacts in the production release repository — Docker image tags, npm package versions, or JAR file names — giving release managers a quick view of what version is currently deployed or staged. The AQL query filters by repository key and sorts by modification date descending.

Prompt example:

```
Run AQL query: items.find({"repo":{"$eq":"docker-release"}, "type":{"$eq":"folder"}}).include("name","path","modified").sort({"$desc":["modified"]}).limit(20); display results in Bubble repeating group with artifact name, path, last modified date
```

### Artifact download statistics for popular packages

An AQL query with statistics.include returns download counts and last download timestamps for each artifact in a public repository. Product managers track which package versions are most actively used in the field without Artifactory admin access.

Prompt example:

```
Run AQL query: items.find({"repo":{"$eq":"npm-public"}}).include("name","stat").sort({"$desc":["stat.downloads"]}).limit(25); display in Bubble repeating group with package name, download count, last download date
```

## Troubleshooting

### All Artifactory API calls return 404 Not Found, including the List Repositories call

Cause: The /artifactory path segment is missing from the Root URL in the API Connector group. The Artifactory REST API is served under the /artifactory path — without it, all endpoint URLs are constructed incorrectly (e.g., /api/repositories becomes yourcompany.jfrog.io/api/repositories instead of yourcompany.jfrog.io/artifactory/api/repositories).

Solution: Open API Connector → JFrog Artifactory group → Root URL. Verify it ends with /artifactory: https://yourcompany.jfrog.io/artifactory (no trailing slash after /artifactory). Delete and re-add the Root URL if needed. Re-initialize any calls that returned 404 after fixing the base URL.

### AQL Search call returns '400 Bad Request' even though the AQL query syntax looks correct

Cause: The AQL endpoint received the request with Content-Type: application/json (the group-level default) instead of the required Content-Type: text/plain. This is the most common AQL integration failure — Artifactory's AQL endpoint rejects any content type other than text/plain with a 400 error regardless of the query content.

Solution: In the API Connector → AQL Search call settings: verify that a call-level Content-Type header is set to 'text/plain' (this header should appear in the individual call's headers, not the group's shared headers). Also verify the Body Type is set to 'Raw' — not 'JSON,' not 'Form Data.' Re-initialize the call after making both changes. If the 400 persists, also check the AQL query text for syntax errors (unmatched brackets, missing quotes around field names).

### 'There was an issue setting up your call' on Initialize — no response from Artifactory

Cause: For self-hosted Artifactory: the server is not publicly reachable from Bubble's servers (private network, firewall blocking the API port, or self-signed SSL certificate). For JFrog Cloud: an incorrect company subdomain in the URL (e.g., yourcompany.jfrog.io vs yourcorrectname.jfrog.io) returns a connection error.

Solution: Test your Artifactory URL from a browser not on your corporate VPN: navigate to https://yourcompany.jfrog.io/artifactory/api/repositories. If you cannot reach it, resolve network accessibility first (Cloudflare Tunnel for self-hosted, or correct the JFrog Cloud subdomain). If you see an SSL certificate warning, the self-hosted Artifactory needs a public CA certificate — Bubble rejects self-signed certs.

### AQL results 'results' array is empty even though artifacts exist in the repository

Cause: The AQL query's repository key does not exactly match the repository name in Artifactory. Repository keys are case-sensitive (docker-local is different from Docker-Local). Additionally, if querying a VIRTUAL repository, AQL may not return expected results — use the underlying LOCAL repository key instead.

Solution: First call 'List Repositories' to get the exact repository key (the 'key' field in the response). Use this exact string (case-sensitive, including hyphens and underscores) in the AQL query's {"repo":{"$eq":"exact-repo-key"}} filter. If the repository is a VIRTUAL repository, identify the LOCAL repositories it aggregates and query those directly.

### Initialize call for 'Get Storage Info' succeeds but Bubble cannot parse the response — no fields detected

Cause: The /api/storageinfo response from a large Artifactory instance may be very large (hundreds of repositories in the repositoriesSummaryList array), causing Bubble's response parser to time out or fail silently before detecting field names.

Solution: If Bubble's Initialize call fails to detect fields from /api/storageinfo, manually inspect the response using Postman with the same auth headers. If the response is very large, consider using the /api/storageinfo?repoKey=docker-local endpoint (if supported in your Artifactory version) to retrieve storage info for one repository at a time during initialization. Once Bubble detects the response shape from a single-repo response, it can handle the full list in workflows.

## Frequently asked questions

### What is AQL and why does it require text/plain instead of JSON?

AQL (Artifactory Query Language) is JFrog's proprietary query language for searching artifacts by any metadata attribute. It looks like a JSON-based query language syntactically, but it is transmitted as raw text — not as a JSON string. The Artifactory /api/search/aql endpoint was designed to accept AQL queries as a plain text body, and the endpoint parser expects to receive the raw AQL text directly. If you JSON-encode the AQL text (wrapping it in quotes as a JSON string), Artifactory's parser receives a JSON document instead of AQL text and returns a 400 error. This is an intentional API design choice, not a bug.

### Can I use Artifactory's AQL to search across multiple repositories in one query?

Yes. The AQL find filter supports $or clauses and the $match operator for pattern matching. To search across multiple specific repositories: items.find({"repo":{"$or":[{"$eq":"docker-local"},{"$eq":"npm-local"}]}}).include("name","path","repo"). To search all repositories matching a pattern: items.find({"repo":{"$match":"*-local"}}) matches all repository keys ending in '-local'.

### Does the JFrog Artifactory integration require a paid Bubble plan?

Read-only API calls (repository listing, AQL searches triggered by button clicks or page load) work on the Bubble free plan. Scheduled Backend Workflows (for caching storage info and repository metadata) require the Starter plan or above. For a simple dashboard that fetches Artifactory data live on page load without persistent caching, the free plan is sufficient — but be aware that /api/storageinfo can be slow and should not be called live per page load on the free plan.

### What is the difference between using an API key (X-JFrog-Art-Api) versus an Access Token (Authorization: Bearer)?

API keys are persistent credentials tied to a user account that do not expire until regenerated. Access Tokens are scoped, expiring credentials that can be limited to specific permissions (read-only, specific repositories) and set to expire after a defined time period. For a Bubble integration used by a team, Access Tokens with a 90-day expiry and read-only scope provide better security hygiene. API keys are simpler to set up and never expire, making them more convenient for personal or low-risk projects.

### Can Bubble write artifacts to Artifactory, or is the integration read-only?

Bubble can make write API calls to Artifactory — for example, deploying files via PUT /repoKey/path/filename or setting artifact properties. However, the practical use-case for Bubble + Artifactory is almost exclusively read-only dashboards. Writing artifacts to Artifactory from a Bubble app would require uploading files (binary artifacts) which involves Bubble's file upload workflow — an unusual pattern. The typical artifact publish flow happens in CI/CD pipelines (Jenkins, Travis CI, GitHub Actions), not from Bubble.

### The AQL response 'results' array returns 0 items even for a repository I know has artifacts. Why?

The most common causes are: (1) the repository key in the AQL query does not exactly match the Artifactory repository key (case-sensitive — 'Docker-Local' is different from 'docker-local'), (2) the repository is a VIRTUAL repository that aggregates other repos — AQL queries work against LOCAL repositories; try querying the constituent LOCAL repo instead of the virtual repo, or (3) the user account whose credentials you are using does not have read permission on the queried repository. Verify with the List Repositories call first to get the exact repository keys.

---

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