Connect FlutterFlow to Printful using a FlutterFlow API Group with Bearer token authentication. Use GET calls for order production status and product catalog reads client-side, but proxy all order-creation and write calls through a Firebase Cloud Function — Printful's Bearer token can authorize real billable charges and must never be embedded in the compiled Flutter app.
| Fact | Value |
|---|---|
| Tool | Printful |
| Category | E-commerce |
| Method | FlutterFlow API Call |
| Difficulty | Intermediate |
| Time required | 40 minutes |
| Last updated | July 2026 |
Build a Printful Order-Tracking and Mockup App in FlutterFlow
Printful is the most popular print-on-demand fulfillment backend for independent creators. It handles printing, packing, and shipping of custom products (T-shirts, mugs, phone cases, and more) only when an order comes in — no inventory required. The FlutterFlow use-case is typically an ops app: a founder wants to see where each order is in the production pipeline, check shipping tracking numbers, and generate product mockup images without leaving their phone.
Printful's REST API is straightforward — Bearer token auth, clean JSON responses, and endpoints for orders, products, sync variants, and the asynchronous mockup generator. The important detail for FlutterFlow is that a Printful Bearer token has full account access: it can create orders, which means real charges hit your credit card. That single fact determines the architecture. Read-only status calls (GET /orders, GET /orders/{id}) are safe to make from the Flutter client, but POST /orders must run through a server-side proxy. The Printful API currently caps traffic at around 120 requests per minute per token — verify the current limit in Printful's developer docs, as this may change.
The mockup generator is the most distinctive Printful feature you can surface in a FlutterFlow app. It is asynchronous: you POST a task, get back a task_key, and then poll GET /mockup-generator/task?task_key= until the status changes from waiting to completed, at which point the response contains your image URL. This two-call pattern requires a polling action loop in FlutterFlow's Action Flow Editor, which this guide walks through in detail.
Integration method
FlutterFlow connects to Printful's REST API (base URL https://api.printful.com) using an API Group with a Bearer token in the Authorization header. Read-only calls for order status, shipping tracking, and product catalog browsing run from the Flutter client over HTTPS. Order creation — which triggers real fulfillment and billing — must be proxied through a Firebase Cloud Function that holds the Bearer token in environment variables, so the secret key never ships inside the compiled app.
Prerequisites
- A Printful account with at least one store connected
- A FlutterFlow project (any plan supports API Calls)
- A Firebase project for the Cloud Function proxy (required for order creation)
- Access to the Printful Dashboard at printful.com/dashboard
- The store ID for your Printful store (found in the store URL or Settings)
Step-by-step guide
Generate a Printful API token
Log in to your Printful account and click your account name or avatar in the top-right corner of the dashboard. From the dropdown, select Settings, then click the Stores tab. Find the store you want to connect to FlutterFlow in the list and click the API link or the Manage button next to it. Scroll to the API section and click Generate Token (the button label may vary slightly between Printful dashboard versions). Printful will display your new Bearer token — copy it immediately and store it in a secure location like a password manager. Tokens are store-scoped, meaning each token is tied to a specific store. If you run multiple stores and need to access them all from your app, you will either need to generate a token per store or use Printful's multi-store header (X-PF-Store-Id) with a single account-level token. Note your store ID from the dashboard URL or the store settings page — you will need it for the X-PF-Store-Id header. Printful tokens do not expire but can be revoked at any time from the same settings page; treat them like a password.
Pro tip: If you have more than one Printful store, add the X-PF-Store-Id header to your API Group and supply the correct numeric store ID. Omitting this header on a multi-store account causes Printful to return data for the wrong store or return a 400 error.
Expected result: You have a Printful Bearer token saved securely and you know your numeric Printful store ID.
Create a Printful API Group in FlutterFlow
In your FlutterFlow project, click API Calls in the left navigation panel. Click + Add → Create API Group. Name the group Printful and set the Base URL to https://api.printful.com. Click the Headers tab and add the following headers. First, add Authorization with the value Bearer [your_token]. Because this is a secret key, do not paste the token as a literal string: click the variable icon next to the value field, create an App State variable named printfulToken (type String, initial value empty), and reference it here as FFAppState().printfulToken. You will set the token value at login or app start through a secure initialization action. Second, add Content-Type with the value application/json. If you are working with a multi-store account, add a third header X-PF-Store-Id and set its value to your numeric store ID (this can be a hardcoded string since it is not a secret). Click Save. The Printful API Group is now ready for individual endpoint calls.
1// Printful API Group configuration (reference)2{3 "group_name": "Printful",4 "base_url": "https://api.printful.com",5 "headers": [6 {7 "key": "Authorization",8 "value": "Bearer [FFAppState().printfulToken]"9 },10 {11 "key": "Content-Type",12 "value": "application/json"13 },14 {15 "key": "X-PF-Store-Id",16 "value": "[your_store_id]"17 }18 ]19}Pro tip: Printful wraps all API responses in a result property. When mapping JSON paths, use $.result[*].id rather than $[*].id for list endpoints like /orders.
Expected result: The Printful API Group appears in the API Calls panel with the base URL, Bearer auth header, and Content-Type header configured.
Add order status and product GET calls and bind to a ListView
With the Printful API Group selected, click + Add API Call. Name the first call GetOrders and set the Method to GET. In the URL Path field, enter /orders. Click the Variables tab and add two optional variables: offset (integer, default 0) and limit (integer, default 20). Update the URL Path to /orders?offset={{offset}}&limit={{limit}}. Click the Response & Test tab, then click Test. Printful wraps responses in a result object, so your raw JSON will look like { "code": 200, "result": [...] }. Click Generate JSON Paths and look for paths like $.result[*].id, $.result[*].status, $.result[*].recipient.name, and $.result[*].shipping. Name these orderId, orderStatus, recipientName, and shippingCarrier. Create a second API Call named GetOrderDetail with URL Path /orders/{{orderId}}, adding orderId as a required variable. Create a third call named GetStoreProducts at /store/products with offset and limit variables, mapping $.result[*].id, $.result[*].name, $.result[*].thumbnail_url, and $.result[*].synced. In your FlutterFlow canvas, add a ListView and configure its Backend Query to use GetOrders. Add a ListTile widget inside the ListView, bind the title to recipientName and the subtitle to orderStatus. Add a color-coded Container behind the status badge: map 'fulfilled' to green, 'in_process' to amber, and 'canceled' to red using a conditional expression in the widget's background color property.
1// Printful JSON path mapping for /orders2// All paths start with $.result because Printful wraps responses34$.result[*].id → orderId5$.result[*].status → orderStatus6$.result[*].recipient.name → recipientName7$.result[*].recipient.email → recipientEmail8$.result[*].shipping → shippingMethod9$.result[*].created → createdTimestamp10$.result[*].shipments[0].tracking_url → trackingUrl1112// /store/products paths13$.result[*].id → storeProductId14$.result[*].name → productName15$.result[*].thumbnail_url → thumbnailUrl16$.result[*].synced → isSyncedPro tip: Printful order statuses follow a predictable progression: draft → pending → in_process → fulfilled. Map each status to a color and icon in your ListView for a clear visual pipeline.
Expected result: A ListView in your FlutterFlow app shows Printful orders with recipient names, production status badges, and shipping information pulled live from the API.
Proxy order creation through a Firebase Cloud Function
Creating a Printful order (POST /orders) triggers real fulfillment and billing. This call must never come from the Flutter client because the Bearer token would be embedded in the compiled app, giving anyone with a decompiler the ability to create unlimited billable orders on your account. Deploy a Firebase Cloud Function named createPrintfulOrder. The function authenticates the caller using Firebase Auth (reject unauthenticated requests with an HttpsError), reads the Printful Bearer token from environment variables, and POSTs the order payload to https://api.printful.com/orders. It returns the Printful order response JSON to FlutterFlow. In FlutterFlow, create a separate API Group named PrintfulProxy that points at your Cloud Function's HTTPS trigger URL. Add a Firebase Auth ID token header so only logged-in app users can call it. In the Action Flow Editor on your checkout screen, wire the Place Order button to call the PrintfulProxy API Call with the order details serialized as JSON. If the response contains a result.id, navigate to a confirmation screen showing the new Printful order ID.
1// Firebase Cloud Function: createPrintfulOrder/index.js2const functions = require('firebase-functions');3const fetch = require('node-fetch');45exports.createPrintfulOrder = functions.https.onCall(async (data, context) => {6 if (!context.auth) {7 throw new functions.https.HttpsError('unauthenticated', 'Login required');8 }910 const token = process.env.PRINTFUL_TOKEN;11 const storeId = process.env.PRINTFUL_STORE_ID;1213 const response = await fetch('https://api.printful.com/orders', {14 method: 'POST',15 headers: {16 'Authorization': `Bearer ${token}`,17 'Content-Type': 'application/json',18 'X-PF-Store-Id': storeId,19 },20 body: JSON.stringify(data.order), // order payload from FlutterFlow21 });2223 if (!response.ok) {24 const error = await response.json();25 throw new functions.https.HttpsError('internal', error.result || 'Printful error');26 }2728 return await response.json();29});Pro tip: Set PRINTFUL_TOKEN and PRINTFUL_STORE_ID using Firebase environment config before deploying. Test with a Printful sandbox or a draft order (confirm=false in the body) before enabling live order creation.
Expected result: The Cloud Function deploys and is callable from FlutterFlow. Test orders are created in Printful (visible in the dashboard) without the Bearer token appearing anywhere in the Flutter app source.
Implement the async mockup generator with polling
The Printful mockup generator is one of the most useful features to surface in a FlutterFlow app. It is a two-step async process: first you submit a task, then you poll until it is complete. In your Printful API Group, create an API Call named SubmitMockupTask with Method POST and URL Path /mockup-generator/create-task/{{productId}}. Add productId as a required variable. The request body should contain a variant_ids array and a files array with your design image URL — see Printful's mockup generator docs for the exact body structure. Map the response path $.result.task_key to taskKey. Create a second API Call named PollMockupTask with Method GET and URL Path /mockup-generator/task?task_key={{taskKey}}, mapping $.result.status to taskStatus and $.result.mockups[0].mockup_url to mockupUrl. In your FlutterFlow Action Flow Editor, wire a Generate Mockup button to first call SubmitMockupTask and store the task_key in a page variable. Then add a Wait action (1–2 seconds) followed by a call to PollMockupTask. Use a Conditional Action to check if taskStatus equals 'completed': if yes, store mockupUrl in an App State variable and display the image; if no, loop back to the Wait + Poll sequence. Add a maximum iteration counter (e.g., 10 tries) to avoid an infinite loop if Printful is slow.
1// POST /mockup-generator/create-task/{productId} body example2// Send this as the API Call body in FlutterFlow3{4 "variant_ids": [4012, 4013],5 "files": [6 {7 "placement": "front",8 "image_url": "https://yourcdn.com/design.png",9 "position": {10 "area_width": 1800,11 "area_height": 2400,12 "width": 1800,13 "height": 1800,14 "top": 300,15 "left": 016 }17 }18 ],19 "format": "jpg"20}2122// Polling response when complete23// $.result.status = "completed"24// $.result.mockups[0].mockup_url = "https://mockup-generator.printful.com/..."Pro tip: Respect Printful's rate limit (approximately 120 requests per minute — verify current limits in Printful's developer docs) when polling. Wait at least 2 seconds between poll calls and show a spinner with status text ('Generating mockup... attempt 3 of 10') so users know the app is working.
Expected result: The app submits a mockup task, polls until complete, and displays the rendered product image URL. The Image widget in FlutterFlow shows the mockup photo as soon as the task_key resolves to a completed status.
Common use cases
POD order tracking dashboard
Build a FlutterFlow app that shows all Printful orders with their production status (pending, in_process, fulfilled, canceled) and the carrier tracking URL. The app pulls from GET /orders filtered by status, displays a status badge with color coding, and opens the tracking URL in an in-app browser when the user taps Track Shipment.
Show me a list of my Printful orders with order ID, product name, current status (pending, in production, shipped), and a Track button that opens the shipping tracking link.
Copy this prompt to try it in FlutterFlow
Product mockup preview generator
Create a FlutterFlow screen where a seller selects a Printful product variant and uploads a design image. The app submits a mockup-generator task to a Firebase Cloud Function, then polls for completion and displays the rendered mockup image. This lets sellers preview new designs before listing them on their storefront.
Let me pick a Printful product and see a photo-realistic mockup with my design applied to it. Show a loading spinner while the mockup is being generated and display the final image when ready.
Copy this prompt to try it in FlutterFlow
Store catalog sync viewer
Build a FlutterFlow app that shows all products synced between a WooCommerce or Shopify store and Printful, including sync status (synced, unsynced, incomplete). Use GET /store/products to list the catalog and GET /store/products/{id} for variant details. Staff can flag items that need attention directly from the app.
Show all my synced Printful products with their sync status, thumbnail image, and number of variants. Let me filter by sync status and tap a product to see the individual variant details.
Copy this prompt to try it in FlutterFlow
Troubleshooting
401 Unauthorized on all Printful API calls
Cause: The Bearer token is incorrect, was revoked in the Printful dashboard, or the App State variable holding it was not initialized before the API call fired.
Solution: Verify the token in the Printful Dashboard → Settings → Stores → API. Confirm the printfulToken App State variable is set at app startup (e.g., in an initial Action on the app's entry page) before any API call fires. Regenerate the token if you suspect it was revoked.
400 Bad Request on all API calls, or data returned is from the wrong store
Cause: You have multiple Printful stores but the X-PF-Store-Id header is missing or contains the wrong store ID.
Solution: Find your numeric store ID in the Printful Dashboard URL (it appears as a number in the URL when you navigate to a specific store) or in Settings → Stores. Add or correct the X-PF-Store-Id header in your FlutterFlow API Group. Each store has a different ID.
429 Too Many Requests when polling the mockup generator rapidly
Cause: The mockup generator polling loop is firing too frequently, exceeding Printful's rate limit of approximately 120 requests per minute per token.
Solution: Add a Wait action of at least 2 seconds between each poll attempt in the Action Flow Editor. Implement a maximum retry counter (10 attempts) and show an error message if the mockup does not complete within that time, prompting the user to try again.
Mockup generator POST returns a task_key but polling always returns status 'waiting' and never 'completed'
Cause: The task_key variable was not correctly passed from the SubmitMockupTask response to the PollMockupTask request, or the polling API Call variable is not mapped to the correct page variable.
Solution: In the Action Flow Editor, after the SubmitMockupTask call, add an Update Page Variable action that sets taskKey to the value returned from the taskKey JSON path. Confirm the PollMockupTask API Call uses this page variable as its taskKey input. Test by checking the raw API response in FlutterFlow's API Call tester to confirm the task_key is in $.result.task_key.
Best practices
- Never put the Printful Bearer token in a FlutterFlow API Call header as a hardcoded literal — store it in App State and initialize it securely at app startup, and keep Write-capable tokens exclusively in Firebase Cloud Function environment variables.
- Proxy all POST /orders calls through a Firebase Cloud Function; a leaked token can drain your Printful balance with unauthorized orders.
- Add the X-PF-Store-Id header to your API Group from the start, even if you only have one store now — it prevents silent data mix-ups if you add a second store later.
- Respect Printful's rate limits when implementing mockup-generator polling — wait at least 2 seconds between poll calls and cap retries at 10 to avoid hammering the API.
- Use Printful's draft order mode (add confirm: false to POST /orders) for testing, so orders are created in Printful but not sent to fulfillment until you explicitly confirm them.
- Cache product catalog data in Firestore or local App State rather than re-fetching the full catalog on every screen load — product data changes infrequently and caching reduces API calls significantly.
- Show clear loading states during mockup generation — the async two-step process can take 10–30 seconds, and users will assume the app is broken without a visible progress indicator.
- Test on a real device or simulator for your final QA pass; FlutterFlow's web Run Mode is fine for API call testing but may behave differently for action loops and wait timers.
Alternatives
WooCommerce is a full storefront and order management API, not a fulfillment backend — choose WooCommerce if you want to manage the entire shopping experience rather than just track print-on-demand production status.
Spocket provides a dropshipping product catalog sourced from US/EU suppliers — choose Spocket if you want to offer branded dropshipped goods rather than custom print-on-demand products.
Ecwid is a hosted storefront platform with its own REST API — choose Ecwid if you want a combined storefront and product listing system rather than Printful's fulfillment-focused API.
Frequently asked questions
Can I use Printful's API without a paid Printful plan?
Yes. Printful's API is free to use — you pay only when an order is fulfilled (print + shipping cost). There are no monthly API fees or subscription tiers for API access. The API token is generated from any Printful account, including the free tier.
Why do I need a Firebase Cloud Function just to create an order?
Because Printful's Bearer token authorizes real monetary charges. If you put the token in your Flutter app, anyone who decompiles the APK or IPA can extract it and place unlimited orders that bill to your payment method. The Cloud Function is a server that holds the token in environment variables (never in code) and only acts on authenticated requests from your app.
How long does the mockup generator take to return a result?
Typically between 5 and 30 seconds depending on server load and product complexity. Always implement a polling loop with a 2-second wait between calls and a maximum retry limit. Show a spinner and progress message so users know the app is working — the async latency is normal behavior, not an error.
Can my FlutterFlow app receive Printful webhooks for real-time order updates?
Not directly — FlutterFlow apps do not have a public inbound URL for webhooks. Configure Printful webhooks to send events to a Firebase Cloud Function, which writes the event to a Firestore collection. Your FlutterFlow app listens to that Firestore collection in real time using FlutterFlow's native Firebase integration and updates the UI when a new event document appears.
What is the X-PF-Store-Id header and do I always need it?
X-PF-Store-Id is required when your Printful account has more than one store connected. Without it, Printful may return data from the wrong store or return a 400 error. Even if you only have one store today, add the header to your API Group from the start so you do not need to refactor later if you expand.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation