# Redis

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: July 2026

## TL;DR

Connect Bubble to Redis using Upstash's HTTP REST API and the Bubble API Connector. Upstash exposes every Redis command as a plain HTTPS GET request, so your Bubble workflows can cache expensive API responses, run rate-limit counters, and store session flags — all without opening a TCP socket. Your Upstash REST token stays server-side in a Private header and never reaches the browser.

## Why Upstash is the Redis bridge Bubble actually needs

Classic Redis clients use a binary protocol over TCP port 6379 with a persistent connection — none of which Bubble's workflow runtime supports. Upstash solves this by exposing every Redis command as a plain HTTPS URL path: `/get/{key}` returns the stored value, `/set/{key}/{value}?ex=3600` stores it with a TTL, `/incr/{key}` increments a counter and returns the new value. Bubble's API Connector treats Upstash exactly like any other REST API.

The primary Bubble use cases for Redis are: (1) caching slow third-party API responses — store a JSON blob with a TTL so repeated Bubble page loads skip the expensive external call; (2) per-user rate-limit counters — INCR a key on each user action, check the count, and block if the threshold is exceeded; (3) session flags — store a simple boolean or token string that drives Bubble conditions without a database round-trip.

Upstash's free tier covers 10,000 commands per day. For most Bubble apps, this is sufficient during development. Production traffic should move to the pay-as-you-go plan priced per 100,000 commands — check current rates at upstash.com/pricing. Note that Bubble's API Workflows (needed for scheduled cache-warming or server-triggered cache invalidation) require a paid Bubble plan and are not available on the Free tier.

## Before you start

- A Bubble app on any plan (Free plan works for basic API Connector usage; paid plan required for API Workflows / scheduled cache warming)
- An Upstash account at console.upstash.com (free tier available)
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' by Bubble → Install)

## Step-by-step guide

### 1. Create an Upstash Redis database and copy your credentials

Go to console.upstash.com and sign in or create a free account. Click 'Create Database' and choose a name and region closest to your users. Select the 'Global' option if your Bubble app serves users in multiple regions. Once the database is created, you land on its detail page. You need two values: the REST URL (it looks like `https://global-loved-hedgehog.upstash.io` — unique to your database) and the REST Token (a long string starting with `A...`). Copy both into a temporary text file. Keep the REST Token private — it has full read and write access to your database, including destructive commands like FLUSHALL. The Upstash free tier allows 10,000 commands per day, resetting at midnight UTC. If your Bubble app runs high traffic, monitor usage in the Upstash console and upgrade to pay-as-you-go before hitting the daily limit.

**Expected result:** You have the Upstash REST URL and REST Token copied and ready. You can optionally test a GET request in your browser: open `https://{your-db-id}.upstash.io/get/testkey` — you should see `{"result":null}` since the key does not exist yet.

### 2. Add Upstash as an API Connector in Bubble with a Private Bearer token

In your Bubble editor, click the Plugins tab in the left sidebar. If 'API Connector' by Bubble is not already listed, click 'Add plugins', search for 'API Connector', and install it. Now click 'API Connector' in your plugins list and then 'Add another API'. Name it 'Upstash Redis'. In the 'Root URL' field, paste your Upstash REST URL: `https://{your-db-id}.upstash.io`. Below the root URL, you see 'Shared headers'. Click 'Add a shared header'. In the Key field type `Authorization`, in the Value field type `Bearer ` followed by your Upstash REST Token (leave a space after 'Bearer'). Most importantly: check the checkbox labeled 'Private'. The Private checkbox tells Bubble to process this header on its servers and never include the raw token value in any client-side code or browser network requests. This is the correct and only safe place for the Upstash REST Token.

```
{
  "root_url": "https://{your-db-id}.upstash.io",
  "shared_headers": [
    {
      "key": "Authorization",
      "value": "Bearer <your-upstash-rest-token>",
      "private": true
    }
  ]
}
```

**Expected result:** The Upstash Redis API group appears in your API Connector plugin with the root URL set and the Authorization header marked Private. No individual calls have been added yet.

### 3. Create the GetKey call and initialize it with a test key

Inside the Upstash Redis API group, click 'Add another call'. Name this call 'GetKey'. Set the method to GET (Upstash REST uses GET for all commands, including reads and writes — this is intentional and correct). In the endpoint field, type `/get/{{key}}`. The `{{key}}` placeholder becomes a dynamic parameter you pass from Bubble workflows. Click the small wrench icon next to `key` to mark it as a 'Path' parameter and give it a default test value like `test_key`. Now click 'Initialize the call'. Bubble needs to make a real, successful HTTP request to detect the response shape. The call will return {"result":null} because `test_key` does not exist in your database — that is a valid response. Click 'Save' on the response shape. Set 'Use as' to 'Data' so Bubble creates a data type from the response. The detected type will have a single field: `result` (text). This is the field you read in subsequent workflow conditions.

```
{
  "method": "GET",
  "endpoint": "/get/{{key}}",
  "params": [
    {
      "key": "key",
      "value": "test_key",
      "type": "path",
      "private": false
    }
  ]
}
```

**Expected result:** The GetKey call initializes successfully and Bubble detects a response with a `result` field. The data type 'GetKey' is now available in your Bubble app for use in workflow conditions and display elements.

### 4. Create SetKey and IncrKey calls

Add a second call named 'SetKey'. Set method to GET, endpoint to `/set/{{key}}/{{value}}`. Add an optional query parameter: key `ex`, value `{{ttl}}` (TTL in seconds, default `3600`). Initialize the call — it returns `{"result":"OK"}`. This confirms the value was stored. Set 'Use as' to 'Action' since SetKey is a write operation you trigger from workflows, not a data source you bind in the UI. Add a third call named 'IncrKey'. Set method to GET, endpoint to `/incr/{{key}}`. Initialize — it returns `{"result":1}` (the new counter value after incrementing). Set 'Use as' to 'Action'. Optionally, add a fourth call 'ExpireKey': GET `/expire/{{key}}/{{seconds}}` for setting TTL on existing keys (used alongside INCR for rolling rate-limit windows). All four calls use GET method — this is the defining feature of Upstash's REST API and the reason it works with Bubble where standard Redis clients do not. Never set these calls to POST — doing so returns 404 errors.

```
// SetKey call
{
  "method": "GET",
  "endpoint": "/set/{{key}}/{{value}}",
  "query_params": [
    { "key": "ex", "value": "{{ttl}}", "default": "3600" }
  ]
}

// IncrKey call
{
  "method": "GET",
  "endpoint": "/incr/{{key}}"
}

// ExpireKey call
{
  "method": "GET",
  "endpoint": "/expire/{{key}}/{{seconds}}"
}
```

**Expected result:** You have four working Upstash API Connector calls: GetKey (Data), SetKey (Action), IncrKey (Action), ExpireKey (Action). All use GET method. You can see them listed under the Upstash Redis API group in your API Connector plugin.

### 5. Build a cache-first Bubble workflow for expensive API calls

This step shows the core Upstash caching pattern in Bubble workflows. Suppose you call a slow third-party API (e.g., a product catalog API) on page load. Open your Bubble workflow editor and create a 'Page is loaded' event workflow. Step 1: Run 'Get data from an external API' → select GetKey → set `key` to `cache_products` (or a dynamic key per user). Step 2: Add a condition branch using 'Only when' on an action: check if the GetKey result's `result` field is not empty. If it IS empty (null), proceed to Step 3. If it has a value, jump to Step 5. Step 3 (cache miss): Call your real third-party API workflow. Step 4: Run SetKey — set `key` to `cache_products`, `value` to the serialized API response, `ttl` to `3600`. Step 5 (both paths converge): Populate your Bubble data source from either the cached or fresh response. This pattern means Bubble only calls the expensive external API once per hour. Measure your Bubble WU consumption before and after enabling the cache to verify savings — the cache is most valuable when the external call is slow and runs on every page load.

**Expected result:** The page load workflow checks Upstash first on every load. On a cache hit, the third-party API call is skipped entirely. On a cache miss, the real API is called and the result is stored in Upstash for the next hour. Your Repeating Group or display element populates correctly in both paths.

### 6. Build a rate-limiting workflow using INCR and EXPIRE

Rate limiting prevents a user from submitting a form, clicking a button, or triggering an action more than N times within a rolling window. In your Bubble workflow editor, create a workflow triggered by a button click (e.g., 'Submit form'). Step 1: Run IncrKey with `key` set to `rate_limit_` + the current user's unique ID (combine 'rate_limit_' text with the Current User's Unique ID field in Bubble's expression builder). The result comes back as a number in the `result` field. Step 2: Add a conditional action that only runs 'Only when IncrKey's result = 1' — run ExpireKey with the same key and `seconds` set to `3600`. This sets the rolling 1-hour window on the first use. Step 3: Add a condition on all subsequent form steps 'Only when IncrKey's result ≤ 5' (or your chosen limit). Step 4: If the count exceeds 5, show an alert popup with an error message and stop the workflow using the 'Terminate this workflow' action. If within the limit, proceed with the form submission steps. Remember: key names must be URL-safe. Construct your key as `rate_limit_` followed by the user ID — do not include email addresses or special characters in key names.

**Expected result:** The rate-limit workflow correctly blocks users after exceeding the threshold within the rolling hour window. The EXPIRE call runs only on the first increment, setting the window. Subsequent increments within the window count toward the limit without resetting it. After the hour expires, the key disappears and the counter resets to zero on the next action.

## Best practices

- Always mark the Upstash REST Token as Private in the Bubble API Connector shared header. The token grants full database access including destructive commands — exposing it client-side would allow any user to delete your entire cache.
- Use underscore-separated key names (cache_product_123, rate_limit_user_abc) and never include colons, slashes, spaces, or user-supplied strings directly in key names without sanitization.
- Set a TTL on every cached key using the `ex` parameter on SetKey. Keys without TTLs accumulate indefinitely and consume storage quota. A 3600-second (1-hour) TTL is a sensible starting point for most cached API responses.
- Cache Upstash responses in Bubble's App State or custom states within a user session. If the same page navigates between sub-pages, re-calling Upstash on each sub-page visit wastes commands against your daily quota.
- Measure Bubble Workload Unit (WU) consumption before and after adding a caching layer. Open the Logs tab in your Bubble editor to view WU usage per workflow run. Caching only provides value if the cached call is slower or more expensive than the cache lookup.
- For scheduled cache-warming workflows (pre-populating Upstash before peak traffic hours), use Bubble's API Workflows with 'Schedule API workflow' — note this requires a paid Bubble plan (Starter or above).
- Design key namespaces to avoid collisions across different Bubble app features. Use a prefix per feature: `cache_` for cached responses, `rate_` for rate limiters, `flag_` for session flags. Document the naming convention in your Bubble app's Settings → General → App description for future team members.

## Use cases

### Cache slow third-party API responses

Your Bubble app calls a weather, stock, or product pricing API on every page load, consuming both Workload Units and external API quota. Store the JSON response in Upstash with a 3600-second TTL using SetKey. On subsequent loads, GetKey first — if the result is not null, use the cached value and skip the external call entirely. This can reduce Bubble WU consumption significantly for high-traffic pages.

Prompt example:

```
Build a Bubble workflow that checks Upstash for a cached weather response for a city before calling the OpenWeather API. If the cache returns null, call OpenWeather, store the result in Upstash with a 1-hour TTL, and display it. If the cache has a value, display it directly.
```

### Per-user rate limiting for form submissions

Prevent spam or abuse by limiting how many times a user can submit a form within an hour. On each submission, increment an Upstash counter keyed by the user's ID (INCR) and set a 3600-second expiry on the first increment (EXPIRE). Compare the returned count to your threshold in a Bubble condition — if exceeded, show an error message and halt the workflow without processing the submission.

Prompt example:

```
Create a Bubble workflow that uses Upstash INCR to count how many times a user has submitted a contact form in the last hour. If the count exceeds 5, display an alert and stop the workflow. If it is exactly 1 (first submission), also run the EXPIRE call to set the 1-hour rolling window.
```

### Session flags and one-time popups

Track lightweight session state that does not need to persist in Bubble's database — such as whether a user has already seen an onboarding modal in their current session. Store a flag in Upstash with a short TTL tied to the user's session identifier. On page load, GetKey checks the flag; if absent, show the modal and SetKey to suppress it for the rest of the session.

Prompt example:

```
Set up a Bubble page load workflow that calls Upstash GetKey with the current user's ID as the key. If the result is null, show the onboarding popup and call SetKey to store 'seen' with a 1800-second TTL. If the result is 'seen', skip the popup entirely.
```

## Troubleshooting

### Initialize call returns 'There was an issue setting up your call' or 404 error

Cause: The most common cause is using the wrong HTTP method. Upstash REST uses GET for every command including SET, INCR, and DEL. If you set a call to POST or PUT, Upstash returns 404. A second common cause is a missing space between 'Bearer' and your token in the Authorization header value.

Solution: Check that every Upstash API Connector call uses the GET method. Verify your Authorization header value is exactly `Bearer ` followed immediately by your token with a single space between them. Also confirm your root URL does not have a trailing slash — it should end with `.upstash.io` not `.upstash.io/`.

### SetKey call returns 404 but GetKey works fine

Cause: The SetKey endpoint includes the value in the URL path (`/set/{{key}}/{{value}}`), so a value containing slashes, spaces, or special characters breaks the URL structure. Bubble passes the value raw into the path.

Solution: URL-encode the value before passing it to SetKey, or restrict cached values to JSON strings without unescaped special characters. For simple strings and JSON blobs, ensure they are serialized as compact JSON (no line breaks). If you need to cache complex data, consider base64-encoding the value first using Bubble's 'Encode text' operator.

### Rate limit counter does not reset after an hour — users stay blocked indefinitely

Cause: The ExpireKey call is missing or is running with `key` as a hardcoded string instead of the dynamic user-specific key. If the key never receives an EXPIRE command, it persists in Upstash forever (or until manually deleted).

Solution: Confirm that your ExpireKey call runs with the 'Only when IncrKey's result = 1' condition so it fires on the first increment. Verify the `key` parameter in the ExpireKey call matches the exact same dynamic expression as IncrKey — both must include the Current User's Unique ID. Test by incrementing the counter manually in the Upstash console and confirming the TTL appears.

### Upstash commands stop working mid-day with 429 errors

Cause: Your Bubble app has exhausted the Upstash free tier of 10,000 commands per day. If caching is called on every page load without checking Bubble's App State first, command count accumulates faster than expected.

Solution: Upgrade to Upstash's pay-as-you-go plan for production apps. As a short-term fix, cache Upstash results in Bubble's App Data or custom states within a session so repeated page navigations within the same session do not re-call Upstash. Check the command count in your Upstash console to understand usage patterns before optimizing.

## Frequently asked questions

### Why does Upstash use GET for SET and INCR commands? That seems backwards.

Upstash's REST API maps every Redis command to a URL path for simplicity. `/set/{key}/{value}` stores a value, `/incr/{key}` increments a counter — all via HTTP GET. This is intentional: it allows any HTTP client (including Bubble's API Connector, browser fetch, and curl) to run Redis commands without special client libraries. The trade-off is that values in the URL path are limited to URL-safe strings. For write operations with complex values, Upstash also supports POST with a JSON body — but the GET approach is simpler for most Bubble use cases.

### Can I use standard Redis with Bubble instead of Upstash?

No. Standard Redis requires a persistent TCP connection over port 6379 using the Redis Serialization Protocol (RESP) — a binary protocol that Bubble's API Connector cannot initiate. Upstash is the practical solution for Bubble because it exposes Redis commands as plain HTTPS GET requests. If you already have a self-hosted Redis instance, you can deploy a thin REST proxy (e.g., a Render Node.js service using the `ioredis` package) that translates HTTPS calls into Redis commands — but Upstash eliminates this complexity for most use cases.

### Do API Workflows (scheduled workflows) in Bubble work with Upstash?

Upstash itself works the same whether called from a Bubble front-end workflow or a Backend API Workflow. The limitation is Bubble-side: API Workflows (including the 'Schedule API workflow' action and recurring events) require a paid Bubble plan (Starter or above) and are not available on the Free plan. If you need to warm your Upstash cache on a schedule (e.g., pre-loading product data every hour), you need at minimum a Bubble Starter plan.

### Is Upstash's free tier enough for a production Bubble app?

The Upstash free tier provides 10,000 commands per day, resetting at midnight UTC. For a low-traffic Bubble app or during development this is adequate. A production app with meaningful traffic can exhaust this quickly — particularly if GetKey runs on every page load without session-level caching in Bubble's App State. Monitor your command count in the Upstash console during your first week of traffic and upgrade to pay-as-you-go before hitting the limit, as 429 errors will cause your caching workflows to fail silently.

### How do I delete a cached key from Upstash in a Bubble workflow?

Add a new API Connector call named 'DeleteKey' with method GET and endpoint `/del/{{key}}`. Initialize it — it returns `{"result":1}` if the key was deleted or `{"result":0}` if the key did not exist. Call this in a Bubble workflow whenever you need to invalidate a cached value — for example, after a user updates their profile, delete the `cache_user_{id}` key so the next page load fetches fresh data.

### What happens to my Upstash data if I exceed the free tier limit?

Upstash stops accepting new commands (returns 429 Too Many Requests) once you exceed the daily free tier command count. Existing data in the database is not deleted. Your Bubble workflows that call Upstash will receive error responses, which you should handle with Bubble's 'Step is not run' condition or a fallback pathway that calls the real API directly. Upgrade to pay-as-you-go in the Upstash console to restore service — the upgrade takes effect immediately.

---

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