Skip to main content
RapidDev - Software Development Agency
bubble-integrationsBubble API Connector

JFrog Artifactory

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.

What you'll learn

  • How to generate a JFrog Artifactory API key or access token and configure the correct auth header for Bubble
  • Why the Artifactory base URL must end with /artifactory and what happens when this path segment is omitted
  • How to configure the AQL search call with Body Type = Raw and Content-Type: text/plain to avoid the 400 error
  • How to write basic AQL queries for artifact search and filter by repository, file type, and modification date
  • How to initialize Bubble API Connector calls against Artifactory and map the nested response fields
  • How to cache Artifactory storage statistics in Bubble's database to avoid slow API responses on dashboard page load
  • Why access tokens are displayed only once in JFrog and how to protect them immediately
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate21 min read1–2 hoursDevOps & ToolsLast updated July 2026RapidDev Engineering Team
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.

Quick facts about this guide
FactValue
ToolJFrog Artifactory
CategoryDevOps & Tools
MethodBubble API Connector
DifficultyIntermediate
Time required1–2 hours
Last updatedJuly 2026

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.

Integration method

Bubble API Connector

The Bubble API Connector calls JFrog Artifactory's REST API at https://{company}.jfrog.io/artifactory with an X-JFrog-Art-Api header (Private) for API key auth, or Authorization: Bearer for access token auth. The AQL search endpoint requires a special setup: Body Type = Raw with a text/plain Content-Type header at the call level.

Prerequisites

  • 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.

artifactory-auth-reference.txt
1// Auth method 1: API Key
2// Location: Artifactory → Profile → Authentication Settings → Generate API Key
3// Header: X-JFrog-Art-Api: YOUR_API_KEY
4// Note: API key is persistent until regenerated/revoked
5
6// Auth method 2: Access Token
7// Location: Administration → Identity and Access → Access Tokens → Generate Token
8// Header: Authorization: Bearer YOUR_ACCESS_TOKEN
9// Note: Displayed only ONCE — store immediately
10// Set scope to: 'applied-permissions/user' or specific repo permissions
11// Set expiry: 90 days recommended for production
12
13// JFrog Cloud base URL format (note the required /artifactory segment):
14// https://yourcompany.jfrog.io/artifactory
15
16// Self-hosted base URL format:
17// https://artifactory.internal.company.com/artifactory
18// (must include /artifactory path segment)
19
20// COMMON MISTAKE: Omitting /artifactory from the base URL
21// Without it, all API endpoints return 404
22// Wrong: https://yourcompany.jfrog.io
23// Right: https://yourcompany.jfrog.io/artifactory

Pro tip: Access tokens shown only once at creation time in JFrog — this applies to both API keys (if using the 'copy once' variant) and Access Tokens. Before leaving the JFrog token generation page, copy the credential to a password manager. If you close the page without copying it, you must generate a new token — there is no 'reveal token' option.

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.

api-connector-artifactory-group.json
1// Bubble API Connector — JFrog Artifactory group configuration
2{
3 "api_group_name": "JFrog Artifactory",
4 "root_url": "https://yourcompany.jfrog.io/artifactory",
5 "shared_headers": [
6 {
7 "key": "X-JFrog-Art-Api",
8 "value": "<private: your-api-key-here>",
9 "private": true,
10 "comment": "Use this for API key auth. Replace with Authorization: Bearer for access token auth."
11 },
12 {
13 "key": "Content-Type",
14 "value": "application/json",
15 "comment": "Default for standard JSON calls. Overridden per-call for AQL (text/plain)."
16 }
17 ]
18}
19
20// Alternative if using Access Token instead of API Key:
21// {
22// "key": "Authorization",
23// "value": "Bearer <private: your-access-token>",
24// "private": true
25// }

Pro tip: The 'Content-Type: application/json' shared header at the group level will be correctly used by the repository listing and storage info calls. For the AQL call, you will add a call-level Content-Type: text/plain header that overrides the group-level header for that specific call only — Bubble applies call-level headers after group-level headers, so call-level values take precedence.

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.

artifactory-list-repos-call.json
1// Bubble API Connector — 'List Repositories' call
2{
3 "call_name": "List Repositories",
4 "method": "GET",
5 "endpoint": "/api/repositories",
6 "parameters": [
7 {
8 "key": "type",
9 "value": "local",
10 "comment": "Optional filter: local | remote | virtual. Remove to get all types."
11 }
12 ],
13 "use_as": "Data"
14}
15
16// Artifactory response (array of repository objects):
17// [
18// {
19// "key": "docker-local",
20// "type": "LOCAL",
21// "description": "Local Docker repository for CI build artifacts",
22// "url": "https://company.jfrog.io/artifactory/docker-local",
23// "packageType": "Docker"
24// },
25// {
26// "key": "npm-proxy",
27// "type": "REMOTE",
28// "description": "Proxy for npmjs.org",
29// "url": "https://company.jfrog.io/artifactory/npm-proxy",
30// "packageType": "Npm"
31// }
32// ]

Pro tip: The 'key' field from the repository list response is the identifier you will use in all subsequent API calls and AQL queries — for example, items.find({"repo":{"$eq":"docker-local"}}). Store the key along with type and packageType in your Bubble ArtifactoryRepo data type so users can filter the dashboard by repository type or package format.

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.'

artifactory-aql-call.json
1// Bubble API Connector — 'AQL Search' call
2// THIS IS THE CRITICAL CONFIGURATION — read carefully
3
4{
5 "call_name": "AQL Search",
6 "method": "POST",
7 "endpoint": "/api/search/aql",
8
9 "call_level_headers": [
10 {
11 "key": "Content-Type",
12 "value": "text/plain",
13 "comment": "REQUIRED: Overrides the group-level application/json header. AQL endpoint ONLY accepts text/plain."
14 }
15 ],
16
17 "body_type": "Raw (text/plain)",
18
19 "body": "items.find({\"repo\":{\"$eq\":\"docker-local\"}}).include(\"name\",\"path\",\"size\",\"modified\").sort({\"$desc\":[\"modified\"]}).limit(25)",
20
21 "use_as": "Data"
22}
23
24// AQL query format (raw text — NOT JSON-encoded):
25// items.find({"repo":{"$eq":"docker-local"}})
26// .include("name","path","size","modified","created")
27// .sort({"$desc":["modified"]})
28// .limit(25)
29
30// Common AQL query examples:
31// Find all Docker image folders in a repo:
32// items.find({"repo":{"$eq":"docker-local"}, "type":{"$eq":"folder"}}).include("name","path","modified").limit(50)
33
34// Find artifacts by file extension:
35// items.find({"repo":{"$eq":"maven-releases"}, "name":{"$match":"*.jar"}}).include("name","size","modified").limit(25)
36
37// Artifactory AQL response structure:
38// {
39// "results": [
40// { "repo": "docker-local", "path": "my-image/1.2.3", "name": ".manifest", "size": 0, "modified": "2026-07-09T10:00:00.000Z" },
41// ...
42// ],
43// "range": { "start_pos": 0, "end_pos": 25, "total": 142 }
44// }

Pro tip: When binding AQL queries to Bubble workflows, make the body a dynamic parameter (mark it as a parameter in the API Connector) so different workflows can pass different AQL queries to the same call. For example, a 'Search by repo' workflow might pass items.find({"repo":{"$eq":"<repo_key>"}})..., while a 'Recent artifacts' workflow passes a time-filtered query. One AQL call handles all search patterns.

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.

artifactory-storage-info-call.json
1// Bubble API Connector — 'Get Storage Info' call
2{
3 "call_name": "Get Storage Info",
4 "method": "GET",
5 "endpoint": "/api/storageinfo",
6 "use_as": "Data"
7}
8
9// Artifactory /api/storageinfo response structure:
10// {
11// "repositoriesSummaryList": [
12// {
13// "repoKey": "docker-local",
14// "repoType": "LOCAL",
15// "foldersCount": 142,
16// "filesCount": 891,
17// "usedSpace": "2.63 GB",
18// "itemsCount": 1033,
19// "packageType": "Docker",
20// "percentageOfTotalSize": "18.2%"
21// }
22// ],
23// "fileStoreSummary": {
24// "storageType": "filesystem",
25// "storageDirectory": "/var/opt/jfrog/artifactory/data",
26// "totalSpace": "500 GB",
27// "usedSpace": "14.44 GB",
28// "freeSpace": "485.56 GB"
29// }
30// }
31
32// Bubble data type: ArtifactoryStorage
33// Fields: repo_key(text), repo_type(text), used_space(text),
34// files_count(number), folders_count(number), last_synced(date)
35
36// Scheduled Backend Workflow: 'Sync Artifactory Storage'
37// Step 1: Call Artifactory > Get Storage Info
38// Step 2: Schedule API Workflow on result.repositoriesSummaryList
39// Sub: upsert ArtifactoryStorage where repo_key = current item's repoKey
40// Schedule: every 86400 seconds (daily)

Pro tip: The 'usedSpace' field from /api/storageinfo is a human-readable string ('2.63 GB', '512 MB') rather than a numeric byte count. For sorting repositories by size in Bubble, you will need to either store this string and sort alphabetically (imperfect) or make a separate AQL call per repository to get numeric byte counts. For a practical dashboard, the human-readable string is usually sufficient and the sorting limitation is acceptable.

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.

artifactory-dashboard-setup.txt
1// Bubble page: artifact-dashboard
2
3// Summary metrics (at page top):
4// Total repositories: Do a search for ArtifactoryStorage:count
5// Total files: Do a search for ArtifactoryStorage:sum of files_count
6// Last storage sync: Do a search for ArtifactoryStorage:first item's last_synced (formatted)
7
8// Primary Repeating Group — Repository List:
9// Type: ArtifactoryStorage
10// Source: Do a search for ArtifactoryStorage, sorted by used_space descending
11// Cells:
12// repo_key → Text element (bold)
13// repo_type → Badge (LOCAL=blue, REMOTE=gray, VIRTUAL=purple)
14// used_space → Text element (right-aligned)
15// files_count → Text element, formatted: formatted as #,###
16// last_synced → Text element, formatted as 'Updated MMM D'
17
18// AQL Search section:
19// Text Input: placeholder 'Enter AQL query or select preset below'
20// Dropdown: preset queries
21// Options: ['Latest Docker Images', 'Recent Maven JARs', 'Large Files (>100MB)']
22// Value templates:
23// 'Latest Docker Images': items.find({"type":{"$eq":"folder"}}).include("name","path","modified").sort({"$desc":["modified"]}).limit(20)
24// 'Recent Maven JARs': items.find({"name":{"$match":"*.jar"}}).include("name","path","size","modified").sort({"$desc":["modified"]}).limit(25)
25
26// AQL results Repeating Group:
27// Workflow: Button click → Call API connector: JFrog Artifactory > AQL Search
28// body = Text Input's value (or Dropdown's selected template)
29// Type: result of AQL Search's results
30// Cells: name, path, size (formatted as MB), modified date

Pro tip: For the preset AQL queries in the Dropdown, store them as Option Set values in Bubble (Data → Option sets → Add option set → AQLQuery → add options with a 'query_text' field for each preset). This avoids hardcoding AQL strings in multiple workflow conditions and makes it easy to add new preset queries without editing individual workflow logic.

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.

Common 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.

Bubble Prompt

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

Copy this prompt to try it in Bubble

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.

Bubble Prompt

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

Copy this prompt to try it in Bubble

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.

Bubble Prompt

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

Copy this prompt to try it in Bubble

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.

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.

Alternatives

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Integrations are where projects stall

We wire Bubble integrations like this one every week — auth, webhooks, edge cases included. Fixed-price builds from $13K, delivered in 6–10 weeks.

Talk to an integration engineer

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.