# Amazon DynamoDB

- Tool: FlutterFlow
- Difficulty: Advanced
- Time required: 75 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Amazon DynamoDB by deploying an API Gateway and Lambda function (or Firebase Cloud Function) as a REST proxy. FlutterFlow points an API Group at your proxy, which holds the AWS IAM credentials and translates simple GET/POST requests into DynamoDB Query and PutItem operations. Unlike Retool's native connector, FlutterFlow has no built-in DynamoDB support — the proxy is mandatory, not optional.

## Query Amazon DynamoDB from FlutterFlow via a Secure REST Proxy

Amazon DynamoDB is AWS's fully managed NoSQL key-value database optimized for single-digit millisecond latency at any scale. It is widely used for e-commerce use cases: shopping cart storage, real-time inventory, order state machines, and session data. The free tier includes 25 GB storage and 25 WCU/25 RCU indefinitely — check aws.amazon.com for current free tier terms as they may change.

Unlike Retool, which has a native DynamoDB connector that handles AWS SigV4 signing internally, FlutterFlow has no DynamoDB support out of the box, and no safe way to sign AWS requests on the Flutter client. The compiled Dart binary would expose IAM credentials to anyone with a decompiler. The only correct architecture is a server-side proxy that holds the credentials and exposes a plain REST API that FlutterFlow calls.

The proxy pattern also gives you an opportunity to design a clean API surface for your app's specific access patterns. DynamoDB requires the exact partition key for a Query — you cannot run arbitrary filters without a slow, expensive Scan. By designing your proxy endpoints around the specific queries your FlutterFlow app needs (e.g., GET /cart/{userId}, GET /orders?date=today), you enforce good DynamoDB query discipline and keep FlutterFlow's integration simple.

## Before you start

- An AWS account (the DynamoDB free tier is sufficient to start)
- A DynamoDB table created in the AWS Console with a meaningful partition key design (e.g., userId, orderId, productId)
- An IAM user or IAM role with only the DynamoDB permissions your app needs (dynamodb:Query, dynamodb:GetItem, dynamodb:PutItem, dynamodb:UpdateItem)
- A Firebase project for the Cloud Function proxy (or AWS API Gateway + Lambda if you prefer a fully-AWS stack)
- A FlutterFlow project on any plan — all plans support API Calls

## Step-by-step guide

### 1. Create a DynamoDB table and IAM policy with least-privilege permissions

Open the AWS Management Console and navigate to DynamoDB. Click Create Table. Choose a table name that reflects your data (e.g., Orders, Inventory, CartItems). The most critical decision is the Partition Key: this is the primary dimension for all queries. Choose a partition key that aligns with your app's most common lookup — for a cart, use userId; for orders, use orderId; for inventory, use productCategory. Optionally add a Sort Key for range queries within a partition (e.g., createdAt for orders under the same userId). Select On Demand capacity mode if traffic is unpredictable; Provisioned mode if you have stable, predictable load and want to stay within the free tier's 25 WCU/25 RCU allocation. Click Create Table. Next, go to IAM in the AWS Console. Click Policies → Create Policy. In the JSON editor, write a policy that grants only the DynamoDB actions your app actually needs on your specific table — use the code block below as a template. Avoid granting dynamodb:Scan (it is expensive and slow on large tables), dynamodb:DeleteTable, or any iam: permissions. Create the policy, then create an IAM User named dynamodb-proxy-user, attach the policy, and generate an Access Key. Store the Access Key ID and Secret Access Key in your Firebase environment config — never in code.

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "FlutterFlowDynamoDBAccess",
      "Effect": "Allow",
      "Action": [
        "dynamodb:Query",
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:YOUR-ACCOUNT-ID:table/YOUR-TABLE-NAME",
        "arn:aws:dynamodb:us-east-1:YOUR-ACCOUNT-ID:table/YOUR-TABLE-NAME/index/*"
      ]
    }
  ]
}
```

**Expected result:** A DynamoDB table exists with your chosen key schema, and an IAM user has an access key with a minimal policy that allows only the necessary DynamoDB operations on that specific table.

### 2. Deploy a Firebase Cloud Function that wraps DynamoDB queries

The Cloud Function is the core of this integration — it is the only component that holds AWS credentials and talks directly to DynamoDB. Create a new Firebase Cloud Function named dynamodbProxy. The function uses the AWS SDK for Node.js (aws-sdk package, available in the Cloud Functions runtime) to create a DynamoDB Document Client, which simplifies working with JavaScript objects instead of DynamoDB's typed attribute format. Implement at minimum two exported functions: queryItems (for fetching a list of items by partition key, using DynamoDB.Query) and getItem (for fetching a single item by primary key, using DynamoDB.GetItem). Add a third putItem function for writes. Each function first validates Firebase Auth to ensure only authenticated app users can query your data. It then reads the request parameters from the callable data object, constructs the DynamoDB parameters (TableName, KeyConditionExpression, ExpressionAttributeValues), calls the AWS SDK, and returns the Items array to FlutterFlow. For pagination, pass through the LastEvaluatedKey if present so FlutterFlow can request the next page. Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and DYNAMODB_TABLE_NAME as Firebase environment variables before deploying.

```
// Firebase Cloud Function: index.js
const functions = require('firebase-functions');
const AWS = require('aws-sdk');

const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: process.env.AWS_REGION,
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
});

const TABLE = process.env.DYNAMODB_TABLE_NAME;

// Query items by partition key
exports.queryItems = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Login required');
  }

  const { partitionKey, partitionValue, limit = 20, lastEvaluatedKey } = data;

  const params = {
    TableName: TABLE,
    KeyConditionExpression: '#pk = :pkVal',
    ExpressionAttributeNames: { '#pk': partitionKey },
    ExpressionAttributeValues: { ':pkVal': partitionValue },
    Limit: limit,
    ExclusiveStartKey: lastEvaluatedKey || undefined,
  };

  const result = await dynamodb.query(params).promise();
  return {
    items: result.Items,
    lastEvaluatedKey: result.LastEvaluatedKey || null,
    count: result.Count,
  };
});

// Get a single item by primary key
exports.getItem = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Login required');
  }

  const { key } = data; // e.g. { userId: 'abc123', orderId: '456' }
  const result = await dynamodb.get({ TableName: TABLE, Key: key }).promise();
  return result.Item || null;
});

// Write a new item or overwrite existing
exports.putItem = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Login required');
  }

  const { item } = data;
  await dynamodb.put({ TableName: TABLE, Item: item }).promise();
  return { success: true };
});
```

**Expected result:** The three Cloud Functions deploy successfully. Testing queryItems in the Firebase Console with a valid partitionKey and partitionValue returns the matching DynamoDB items as a plain JSON array.

### 3. Create a FlutterFlow API Group pointing at the proxy

In FlutterFlow, click API Calls in the left navigation panel. Click + Add → Create API Group. Name the group DynamoDBProxy. Set the Base URL to your Firebase Cloud Function's HTTPS trigger base URL — this is the URL up to and including the region and project path, but not the specific function name (or use the full callable URL pattern). For Firebase callable functions, the URL format is https://us-central1-YOUR-PROJECT-ID.cloudfunctions.net/functionName. Add a header Authorization and set its value to Bearer {{idToken}} — reference an App State variable ebayIdToken (or use FlutterFlow's built-in Firebase Auth current user's ID token). Now create three API Calls inside this group: one for QueryItems (POST to the queryItems function URL, body contains partitionKey, partitionValue, limit, and lastEvaluatedKey), one for GetItem (POST to the getItem function URL, body contains the key object), and one for PutItem (POST to the putItem function URL, body contains the item object). In the Response & Test tab for QueryItems, add a sample response and click Generate JSON Paths to map $.items[*] fields to FlutterFlow data model fields. Your exact JSON paths depend on your DynamoDB table's attribute names — for an orders table you might map $.items[*].orderId, $.items[*].status, $.items[*].total, $.items[*].createdAt.

```
// API Group: DynamoDBProxy
// Configuration reference
{
  "group_name": "DynamoDBProxy",
  "base_url": "https://us-central1-YOUR-PROJECT-ID.cloudfunctions.net",
  "headers": [
    {
      "key": "Content-Type",
      "value": "application/json"
    }
  ],
  "api_calls": [
    {
      "name": "QueryItems",
      "method": "POST",
      "path": "/queryItems",
      "body": {
        "data": {
          "partitionKey": "{{partitionKeyName}}",
          "partitionValue": "{{partitionKeyValue}}",
          "limit": "{{limit}}",
          "lastEvaluatedKey": "{{lastEvaluatedKey}}"
        }
      }
    },
    {
      "name": "GetItem",
      "method": "POST",
      "path": "/getItem",
      "body": {
        "data": {
          "key": "{{primaryKey}}"
        }
      }
    },
    {
      "name": "PutItem",
      "method": "POST",
      "path": "/putItem",
      "body": {
        "data": {
          "item": "{{itemData}}"
        }
      }
    }
  ]
}
```

**Expected result:** The DynamoDBProxy API Group appears in FlutterFlow's API Calls panel with three API Calls. Testing QueryItems with a valid partitionKeyName and partitionKeyValue in the Response & Test tab returns items from DynamoDB.

### 4. Render DynamoDB items in a FlutterFlow ListView and handle pagination

In your FlutterFlow canvas, navigate to the page where you want to display DynamoDB data. Add a ListView widget. In the Backend Query settings for the ListView, select API Call, choose the DynamoDBProxy group, and select QueryItems. Set the partitionKeyName to match your table's partition key attribute name (e.g., 'userId') and set partitionKeyValue to the current user's Firebase Auth UID using FFAppState().currentUser.uid or the authenticated user's ID from FlutterFlow's Firebase Auth integration. Set limit to 20. Add the data model fields inside the ListView template widget and bind each Text or Image widget to the corresponding JSON path field. DynamoDB paginates via LastEvaluatedKey rather than page numbers. To implement Load More pagination: create a page variable lastKey (type JSON) initialized to null. After a successful QueryItems call, check if the response contains a non-null lastEvaluatedKey field. If it does, store it in the lastKey page variable and show a Load More button. When the user taps Load More, call QueryItems again, passing the lastKey as the lastEvaluatedKey parameter. Append the new items to your list rather than replacing it. When lastEvaluatedKey comes back null from DynamoDB, hide the Load More button.

```
// DynamoDB pagination pseudocode for Action Flow Editor

// Initial load (On Page Load action)
// 1. Call QueryItems
//    partitionKeyName: 'userId'
//    partitionKeyValue: FFAppState().currentUserId
//    limit: 20
//    lastEvaluatedKey: null
// 2. Store response.items → pageVariable.itemList
// 3. Store response.lastEvaluatedKey → pageVariable.lastKey
// 4. IF pageVariable.lastKey != null THEN show Load More button

// Load More button action
// 1. Call QueryItems
//    lastEvaluatedKey: pageVariable.lastKey (JSON object from previous response)
// 2. APPEND response.items to pageVariable.itemList
// 3. UPDATE pageVariable.lastKey = response.lastEvaluatedKey
// 4. IF pageVariable.lastKey == null THEN hide Load More button

// Example JSON paths for orders table
// $.result.items[*].orderId      → orderId
// $.result.items[*].status       → orderStatus
// $.result.items[*].total        → total
// $.result.items[*].createdAt    → createdAt
// $.result.lastEvaluatedKey      → lastEvaluatedKey (pass back for next page)
```

**Expected result:** The ListView displays DynamoDB items for the current user's partition key. The Load More button appears when there are additional pages and disappears when the last page is reached.

### 5. Write data to DynamoDB and handle on-demand vs provisioned capacity errors

Writing items to DynamoDB from FlutterFlow follows the same pattern as reads. In the Action Flow Editor on your data-entry screen, wire the Save button to call the PutItem API Call, constructing the item JSON object from the screen's form fields. PutItem overwrites an existing item if the primary key matches, or creates a new one if the key is new — there is no separate 'create' vs 'update' for DynamoDB PutItem. If you need conditional writes (e.g., only create if the item does not already exist), implement a ConditionExpression in your Cloud Function (attribute_not_exists(partitionKey)) and handle the ConditionalCheckFailedException in the function by returning a meaningful error response that FlutterFlow can check. For throttling: if your table uses Provisioned capacity and traffic exceeds your WCU/RCU allocation, DynamoDB returns a ProvisionedThroughputExceededException, which surfaces as a 400 error from your proxy. Handle this in FlutterFlow by checking the response status code and showing a 'Server busy, please try again' message, then retrying after a short wait. On-demand capacity mode never throttles but costs more per request.

```
// Conditional PutItem in Cloud Function (prevent duplicate keys)
exports.putItemSafe = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Login required');
  }

  const { item, allowOverwrite = true } = data;
  const params = {
    TableName: TABLE,
    Item: item,
  };

  // Prevent overwriting existing items if allowOverwrite is false
  if (!allowOverwrite) {
    params.ConditionExpression = 'attribute_not_exists(#pk)';
    params.ExpressionAttributeNames = { '#pk': Object.keys(item)[0] };
  }

  try {
    await dynamodb.put(params).promise();
    return { success: true };
  } catch (error) {
    if (error.code === 'ConditionalCheckFailedException') {
      throw new functions.https.HttpsError('already-exists', 'Item already exists');
    }
    if (error.code === 'ProvisionedThroughputExceededException') {
      throw new functions.https.HttpsError('resource-exhausted', 'Database busy, retry');
    }
    throw new functions.https.HttpsError('internal', error.message);
  }
});
```

**Expected result:** Submitting the form in FlutterFlow triggers the PutItem Cloud Function, which writes the item to DynamoDB. The item appears when the page is refreshed and the QueryItems call runs again.

## Best practices

- Never embed AWS IAM Access Keys in FlutterFlow — always use a Firebase Cloud Function or AWS Lambda as the proxy; IAM credentials in a Flutter binary are a critical security vulnerability.
- Design your DynamoDB partition key schema around your app's query patterns before building screens — changing the primary key later requires creating a new table and migrating data.
- Expose Query operations from your proxy, not Scan — Scan reads every item in the table and is both slow and expensive; Query uses the partition key index and returns results in milliseconds.
- Always use the Limit parameter in DynamoDB Queries and pass LastEvaluatedKey back to FlutterFlow for pagination — unbounded queries on large partitions can cause Cloud Function timeouts.
- Use IAM least-privilege access: grant only dynamodb:Query, dynamodb:GetItem, dynamodb:PutItem, and dynamodb:UpdateItem on your specific table ARN — never dynamodb:* or Scan in production.
- Consider On Demand capacity mode for development and early production to avoid ProvisionedThroughputExceededException errors when traffic patterns are unpredictable.
- Use DynamoDB's Document Client (not the raw DynamoDB client) in your Cloud Function — it automatically handles JavaScript-to-DynamoDB type marshalling, making queries and inserts much simpler.
- Test with small datasets first and add Global Secondary Indexes (GSIs) for any additional access patterns you need beyond the primary key — GSIs let you query on non-key attributes without a Scan.

## Use cases

### Real-time shopping cart stored in DynamoDB

Build a FlutterFlow e-commerce app that reads and writes a user's shopping cart to a DynamoDB table keyed by userId. The app fetches cart items via a proxy GET endpoint that runs a DynamoDB Query on the partition key, and adds items via a proxy POST endpoint that calls PutItem. Cart updates appear immediately in the app's ListView without page refresh.

Prompt example:

```
Show my shopping cart items from the database with product name, quantity, and price. Let me add a new item, update the quantity, or remove an item, with changes saved instantly.
```

### Inventory tracking dashboard with low-stock alerts

Create a FlutterFlow app for warehouse staff that queries a DynamoDB inventory table by product category (partition key) and flags items where stock_count falls below a threshold. The proxy endpoint runs a Query with a filter expression on the sort key range. Staff can update stock counts through a separate PUT endpoint that calls DynamoDB UpdateItem.

Prompt example:

```
Show me all products in a selected category with their current stock count. Highlight items with stock below 10 in red. Let me tap an item to update its stock count after a restock.
```

### Order status lookup by order ID

Build a FlutterFlow order-tracking screen where customers enter their order ID and the app fetches the order from a DynamoDB table using GetItem with the orderId as the partition key. The proxy returns order status, items, estimated delivery date, and tracking number. This single-item lookup is DynamoDB's fastest read pattern and requires no Scan.

Prompt example:

```
Let me enter an order ID and see the order details: status, items ordered, delivery date, and tracking number. Show an error message if the order ID is not found.
```

## Troubleshooting

### Cloud Function returns items: [] or count: 0 even though data exists in DynamoDB

Cause: The partition key name or value passed from FlutterFlow does not exactly match the attribute name and value in DynamoDB. DynamoDB is case-sensitive: 'userId' and 'UserID' are different keys.

Solution: Open the DynamoDB Console, navigate to your table, click Explore Table Items, and verify the exact attribute name and a sample value. Compare these exactly against what FlutterFlow is sending in the QueryItems request body. Confirm the KeyConditionExpression in your Cloud Function uses the same attribute name as the table's partition key.

### 400 ProvisionedThroughputExceededException returned by the proxy under load

Cause: The DynamoDB table is in Provisioned capacity mode and the number of reads or writes per second has exceeded the configured WCU or RCU allocation. This can happen on the free tier (25 WCU/25 RCU) under normal app traffic.

Solution: Switch the table to On Demand capacity mode in the DynamoDB Console (Table → Additional Settings → Edit Capacity) if you need to handle unpredictable spikes. Alternatively, increase the Provisioned throughput values for steady-state high traffic. In FlutterFlow, add error handling in the Action Flow Editor that shows a retry prompt when the proxy returns this error code.

### Cloud Function times out when querying a large partition

Cause: A DynamoDB partition with a very large number of items causes the Query to take longer than the Cloud Function's default timeout (60 seconds for Firebase callable functions), especially if you are not using the Limit parameter.

Solution: Always include a Limit parameter in your DynamoDB Query (the proxy accepts it as a variable from FlutterFlow). A limit of 20–50 items per page is typical for a mobile ListView. Never request unbounded results from a DynamoDB Query in a Cloud Function that has a timeout.

### FlutterFlow API Call returns 401 or 'unauthenticated' from the Cloud Function

Cause: The Firebase Auth session has expired, or the FlutterFlow app is calling the Cloud Function before the user has logged in.

Solution: Firebase callable functions check context.auth automatically. Ensure the user is authenticated in FlutterFlow (check isAuthenticated state before navigating to the DynamoDB screen) and that the Firebase Auth session is valid. If sessions expire, add an on-page-load check that refreshes the Auth token before calling the proxy.

## Frequently asked questions

### Why can't I call DynamoDB directly from FlutterFlow without a proxy?

DynamoDB uses AWS SigV4 authentication, which requires an AWS Secret Access Key to sign every request. Signing in Dart code running on the user's device means the secret key is embedded in your compiled Flutter app, where anyone can extract it with a decompiler. A leaked IAM key gives an attacker read and write access to your DynamoDB table. The proxy pattern (Cloud Function or Lambda) keeps the key exclusively on the server and is the only safe approach for client-compiled apps.

### How is this different from Retool's DynamoDB integration?

Retool is a server-side tool — it runs in a browser connected to Retool's own servers, which handle the AWS signing internally through Retool's Resources system. Retool never compiles code to a mobile app that users install, so it can hold credentials server-side naturally. FlutterFlow compiles to a Flutter app that runs entirely on the user's device, inverting the security model and requiring an explicit proxy step.

### Can I use AWS API Gateway and Lambda instead of Firebase Cloud Functions?

Absolutely. AWS API Gateway + Lambda is a fully-AWS alternative that works well if your team is already using AWS. Create a Lambda function that uses the AWS SDK, expose it through API Gateway, protect it with an API Gateway API key or AWS Cognito, and point FlutterFlow's API Group at the API Gateway endpoint. The FlutterFlow setup is identical — it just calls a different HTTPS URL. The Firebase Cloud Function approach is covered here because FlutterFlow has deep native Firebase support, making user authentication easier to wire up.

### What is LastEvaluatedKey and why is it different from page numbers?

DynamoDB does not support offset-based pagination (page 1, page 2, etc.) because it does not know the total size of a query result until it has scanned through all items. Instead, it returns a LastEvaluatedKey object identifying where the current page ended. To fetch the next page, you pass this key back as the ExclusiveStartKey in your next query. When DynamoDB returns no LastEvaluatedKey, you have reached the last page. Store the LastEvaluatedKey in a FlutterFlow page variable as a JSON type and pass it unmodified to the next API call.

### Is there an easier way to connect FlutterFlow to structured AWS data?

If you do not need DynamoDB specifically, consider using Supabase (PostgreSQL) or Firebase Firestore as your primary database — both have native FlutterFlow connectors that eliminate the need for a custom proxy. If your existing system already stores data in DynamoDB and you need to read from it in a FlutterFlow app, the proxy pattern in this guide is the right approach. If you'd like RapidDev to set up the complete proxy and FlutterFlow integration for you, book a free scoping call at rapidevelopers.com/contact.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/amazon-dynamodb
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/amazon-dynamodb
