Connecting Bubble to Printful uses a simple Bearer token in a Private API Connector header — no OAuth flow, no token expiry. The integration splits into two directions: outbound (Bubble places a Printful order when a customer checks out via Stripe) and inbound (Printful webhooks notify Bubble when an order ships). Inbound webhooks require a Bubble Backend Workflow on a paid plan. Always confirm Stripe payment before triggering the Printful order — Printful charges your account immediately.
| Fact | Value |
|---|---|
| Tool | Printful |
| Category | E-commerce |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 1–2 hours |
| Last updated | July 2026 |
Building a POD storefront in Bubble — Printful handles production, Bubble handles the experience
Printful is the most popular print-on-demand backend for Bubble storefronts. The pattern is straightforward: your Bubble app presents the product catalog (sync products with variants and mockup images), a customer selects items and completes a Stripe checkout, and your Bubble workflow places the order with Printful immediately after payment confirmation. Printful prints and ships the item directly to the customer.
The Printful API requires only a Bearer token — no OAuth, no token rotation. This makes it significantly easier to configure in Bubble than marketplace APIs like eBay. The token goes in the Authorization header of every Printful API call, marked Private in Bubble's API Connector so it never reaches the browser.
There are two important Printful response patterns that trip up beginners. First, every Printful API response wraps data inside a `result` key: `{ "code": 200, "result": { ... } }`. All Bubble API Connector response mappings must point to `result.xxx`, not the top-level field. If you skip this, all your Repeating Group bindings return empty.
Second, Printful timestamps are Unix epoch seconds (e.g., `1737982853`) rather than ISO 8601 dates. Bubble's native Date fields cannot parse Unix seconds directly — you'll need to handle this when displaying order dates.
For order status updates (knowing when Printful ships an order), Printful sends webhook events to a URL you register. In Bubble, this means a Backend API Workflow endpoint — available only on paid Bubble plans. On the free plan, you must poll the orders endpoint instead.
Integration method
Bubble API Connector with a Printful Bearer token in a Private Authorization header for outbound catalog and order calls; a Bubble Backend Workflow endpoint for inbound Printful order-status webhooks.
Prerequisites
- A Printful account at printful.com with at least one sync product configured (with variants and mockups)
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' → Install)
- For inbound webhooks (order status updates): a Bubble paid plan (Starter or above) and the Workflow API enabled in Settings → API
- Stripe integration configured in Bubble for payment processing before Printful order creation — Printful charges your account immediately on order placement
Step-by-step guide
Generate your Printful API token
Log in to your Printful account at printful.com. Navigate to Settings in the top-right account menu. In Settings, click 'Stores' in the left sidebar, then select the store you want to connect to Bubble. Under the store settings, look for the 'API' tab or 'API Access' section. Click 'Enable API Access' if it is not already enabled. Printful generates an API token unique to this store. Copy it immediately — this token provides full API access to your Printful store data and order management. Alternatively, for account-level access across all your Printful stores, go to Settings → API (the top-level account setting, not a specific store). An account-level token lets you manage multiple stores from the same Bubble app — recommended if you plan to expand to multiple storefronts. Do not paste this token anywhere in your Bubble app's frontend code, data fields, or page states. You will add it directly to the Bubble API Connector as a Private header value in the next step.
1// Printful API token format:2// Your token is a long alphanumeric string (example format):3// 7f3k9m2p1q8r5n6t4w0x45// Base URL for all Printful API calls:6// https://api.printful.com78// Quick test (browser console or REST client):9// GET https://api.printful.com/stores10// Authorization: Bearer YOUR_TOKEN_HERE11//12// Expected response:13// { "code": 200, "result": [ { "id": 123456, "name": "My Store", ... } ] }Pro tip: If you are testing a new Printful store setup, use a separate test token or Printful's sandbox environment if available. Do not use your production Printful token during development — any POST /orders call made with a real token creates a real Printful order and charges your Printful wallet immediately.
Expected result: You have a Printful API token (alphanumeric string) ready to paste into Bubble's API Connector. The token is not pasted anywhere in the Bubble editor yet — just saved to a local text file for now.
Configure the Bubble API Connector for Printful
In your Bubble editor, go to Plugins tab → API Connector. If API Connector is not installed, click 'Add plugins' → search 'API Connector' → Install it (it's free, by Bubble). Click 'Add another API' and name it 'Printful.' Set the base URL to `https://api.printful.com`. Do NOT add a trailing slash. In the shared headers section for this API group, add: - Header name: `Authorization`, Value: `Bearer YOUR_TOKEN_HERE` — paste your actual Printful token, then check the **Private** checkbox. This is the most important step: Private means Bubble sends this header only from its servers, never from the user's browser. - Header name: `Content-Type`, Value: `application/json` Now add your first API call for initialization. Click 'Add another call' and name it 'Get Stores.' Set method to GET and URL to `/stores`. This is a simple endpoint that returns your Printful store list — safe to use as an Initialize Call because it never creates or modifies data. Click 'Initialize call.' You should see a JSON response like `{ "code": 200, "result": [ { "id": 123456, "name": "My Store", ... } ] }`. Bubble maps the detected fields including `result.[0].id` and `result.[0].name`. If the Initialize call returns an empty response or error, check that the Authorization header includes 'Bearer ' followed by your token with no extra spaces.
1// Printful API Connector configuration:2// API name: Printful3// Base URL: https://api.printful.com4//5// Shared Headers:6// Authorization: Bearer YOUR_PRINTFUL_TOKEN // mark Private7// Content-Type: application/json8//9// Initialize Call: GET /stores10// Expected response:11// {12// "code": 200,13// "result": [14// {15// "id": 123456,16// "name": "My Bubble Store",17// "type": "manual",18// "website": "https://myapp.bubbleapps.io"19// }20// ]21// }Pro tip: If you see 'There was an issue setting up your call' during Initialize, the most common causes are: (1) missing 'Bearer ' prefix before your token in the Authorization header, (2) the token has an extra space or newline character from copy-pasting. Re-enter the token manually if needed. The Initialize call must return a real 200 response with data for Bubble to detect the response schema.
Expected result: The API Connector shows 'Printful' with the shared Authorization header marked Private. The Initialize Call returns a 200 response showing your Printful store list. Bubble has detected the `result` array and its nested fields.
Build the product catalog calls and display products in a Repeating Group
With the Printful connector initialized, add the product catalog calls. Click 'Add another call' under the Printful API group and name it 'Get Sync Products.' Set method to GET and URL to `/sync/products`. Add parameters: - `store_id` (text, optional) — your Printful store ID; add it if you have multiple stores - `limit` (text, default '20') — products per page, maximum 100 - `offset` (text, default '0') — pagination offset Click Initialize call. Printful returns a response like `{ "code": 200, "result": [ { "id": 1234, "name": "Classic T-Shirt", "thumbnail_url": "https://..." } ], "paging": { "total": 45, "limit": 20, "offset": 0 } }`. Bubble maps response fields under `result.[n].id`, `result.[n].name`, `result.[n].thumbnail_url`. Note: **all mappings must start with `result.`** — this is Printful's response wrapper pattern. For individual product details including variants (sizes, colors, prices, mockup images), add another call named 'Get Sync Product Detail.' Set method GET, URL `/sync/products/<id>` where `<id>` is a dynamic text parameter. The response includes `result.sync_product` (product info) and `result.sync_variants` (array of all variants with variant_id, sku, retail_price, and files). On your Bubble storefront page, add a Repeating Group. Set its data source to 'Plugins → API Connector → Printful → Get Sync Products' and map `result` as the list. In each cell, bind: - Product name: current cell's `name` field - Image: current cell's `thumbnail_url` field (use Bubble's Image element, URL source) - On cell click: navigate to a product detail page, passing the product `id` as a URL parameter
1// Printful → Get Sync Products2// Method: GET3// URL: https://api.printful.com/sync/products4//5// Parameters:6// limit: 207// offset: <dynamic: pagination_offset state variable>8//9// Response structure:10// {11// "code": 200,12// "result": [13// {14// "id": 1234567,15// "name": "Classic Unisex T-Shirt",16// "thumbnail_url": "https://files.cdn.printful.com/files/...",17// "is_ignored": false18// }19// ],20// "paging": { "total": 45, "limit": 20, "offset": 0 }21// }2223// Printful → Get Sync Product Detail24// Method: GET25// URL: https://api.printful.com/sync/products/<id>26// (<id> = dynamic text parameter for product ID)27//28// Response:29// {30// "code": 200,31// "result": {32// "sync_product": { "id": 1234567, "name": "Classic Unisex T-Shirt" },33// "sync_variants": [34// {35// "id": 9876543,36// "name": "Classic Unisex T-Shirt / S / Black",37// "retail_price": "29.99",38// "sku": "MYSTORE-TSHIRT-S-BLACK",39// "files": [ { "type": "preview", "preview_url": "https://..." } ]40// }41// ]42// }43// }Pro tip: Printful's sync_variants array includes a 'files' array with mockup preview images. The preview file has `type: 'preview'` and a `preview_url` that you can display directly in Bubble as a product mockup image. Iterate over sync_variants to build a size/color selector — each variant has its own variant_id which you'll need when placing an order.
Expected result: Your Bubble product page shows a Repeating Group populated with Printful product thumbnails and names. Clicking a product loads its variants from the detail call, showing available sizes, colors, and retail prices.
Place a Printful order after Stripe payment confirmation
The most critical workflow sequence in a POD storefront is: Stripe payment confirmed → Printful order placed. Always confirm payment first — Printful charges your Printful wallet immediately when an order is created. Add a new API Connector call named 'Place Printful Order.' Set method POST, URL `/orders`. Set the body type to 'JSON body.' The required fields are `recipient` (customer shipping address) and `items` (array of ordered variants with quantities). In Bubble, this POST call body must be constructed dynamically using the API Connector's JSON body parameters. Add these top-level parameters to the call: - `recipient` (object type) — you'll need to use a JSON text parameter and build the object string dynamically - Alternatively, use Bubble's 'Body (raw)' option and build the full JSON as a dynamic text expression For the 'Body (raw)' approach, set content type to JSON and build the body text dynamically in Bubble's expression editor: ``` {"recipient":{"name":"[Customer Name]","address1":"[Street]","city":"[City]","state_code":"[State]","country_code":"US","zip":"[Zip]"},"items":[{"sync_variant_id":[Variant ID],"quantity":[Quantity]}]} ``` In your Stripe payment workflow, after the payment step confirms success (Stripe webhook received or Stripe.js payment confirmed), add this Printful order action. Map customer fields from your Bubble User data type and the selected variant ID from the cart state. Initialize the call with a real test payload — use your personal address and a real sync_variant_id from your Printful catalog. Printful will create a real draft order (set `draft: true` in the body during testing to prevent automatic fulfillment).
1// Printful → Place Order2// Method: POST3// URL: https://api.printful.com/orders4// Body type: JSON (raw)5//6// Body:7{8 "recipient": {9 "name": "<dynamic: Customer's name>",10 "address1": "<dynamic: Customer's street address>",11 "city": "<dynamic: Customer's city>",12 "state_code": "<dynamic: Customer's state, 2-letter code>",13 "country_code": "US",14 "zip": "<dynamic: Customer's zip code>",15 "email": "<dynamic: Customer's email>",16 "phone": "<dynamic: Customer's phone>"17 },18 "items": [19 {20 "sync_variant_id": <dynamic: selected Printful sync_variant_id>,21 "quantity": <dynamic: quantity from cart>22 }23 ],24 "retail_costs": {25 "currency": "USD",26 "subtotal": "<dynamic: cart subtotal>",27 "shipping": "<dynamic: selected shipping rate>",28 "total": "<dynamic: cart total>"29 }30}3132// Add 'draft: true' during testing to prevent auto-fulfillment:33// {34// "draft": true,35// "recipient": { ... },36// "items": [ ... ]37// }Pro tip: Always run the Printful POST /orders call inside a Backend Workflow that is triggered ONLY after a Stripe payment_intent.succeeded webhook is received and verified. Never trigger the Printful order placement from a client-side button click or a frontend Stripe.js confirmation — network errors or browser navigation can interrupt the workflow, leaving a paid order without fulfillment.
Expected result: After completing a test Stripe payment, the Printful order appears in your Printful dashboard under Orders. The order shows the correct product, variant, recipient address, and is in 'Draft' status if you used the draft:true flag. Remove draft:true before going live.
Estimate shipping costs before checkout
Show customers real shipping rates from Printful before they complete checkout. Printful's `/shipping/rates` endpoint accepts a recipient address and items array and returns all available shipping options with rates and delivery estimates. Add a call named 'Get Shipping Rates.' Set method POST, URL `/shipping/rates`. Build the JSON body with `recipient` (same structure as the order body — address fields) and `items` (array of objects with `quantity`, `variant_id`, and `value` — the retail price used for insurance). Call this endpoint when the customer lands on the checkout page or after they enter their shipping address. Display the returned array of rate options in a Repeating Group or Dropdown, showing carrier name, service level, rate (cost), and estimated delivery window. Store the customer's selected shipping option in a custom state variable (carrier name and rate amount), then include `retail_costs.shipping` in the Printful order POST body when placing the order. This ensures Printful's records show the correct shipping charge collected. Note: Printful shipping rates are calculated based on the recipient's location and the package dimensions of the specific variants ordered. Rates change frequently — always fetch them live rather than caching them, so customers see accurate costs.
1// Printful → Get Shipping Rates2// Method: POST3// URL: https://api.printful.com/shipping/rates4//5// Body:6{7 "recipient": {8 "address1": "<dynamic: entered street address>",9 "city": "<dynamic: entered city>",10 "state_code": "<dynamic: entered state>",11 "country_code": "US",12 "zip": "<dynamic: entered zip code>"13 },14 "items": [15 {16 "quantity": <dynamic: cart quantity>,17 "variant_id": <dynamic: Printful variant_id (not sync_variant_id)>,18 "value": "<dynamic: item retail price as string>"19 }20 ],21 "currency": "USD",22 "locale": "en_US"23}2425// Response:26// {27// "code": 200,28// "result": [29// {30// "id": "STANDARD",31// "name": "Flat Rate (3-4 business days after fulfillment)",32// "rate": "3.99",33// "currency": "USD",34// "minDeliveryDays": 3,35// "maxDeliveryDays": 436// },37// {38// "id": "EXPRESS",39// "name": "Express (1-2 business days after fulfillment)",40// "rate": "7.99",41// "currency": "USD"42// }43// ]44// }Pro tip: The `variant_id` in the shipping rates call is Printful's internal catalog variant ID, NOT the sync_variant_id from your store sync. Find the catalog variant_id in the sync product detail response under `sync_variants[n].variant_id`. This is a common point of confusion — sync_variant_id is store-specific; variant_id is catalog-level.
Expected result: When a customer enters their shipping address on the checkout page, a Repeating Group or Dropdown shows available Printful shipping options with their names, rates, and delivery estimates. The customer can select a shipping method before proceeding to Stripe payment.
Configure inbound Printful webhooks via a Bubble Backend Workflow
Printful sends webhook events to notify your Bubble app when orders change status — the most important events are `package_shipped` (with tracking number) and `order_updated` (status changes). To receive these, you need a Bubble Backend Workflow endpoint. First, ensure the Workflow API is enabled: go to Settings → API → check 'This app exposes a Workflow API.' Your app's workflow endpoint base URL will be shown (format: `https://yourapp.bubbleapps.io/api/1.1/wf/`). In the Backend Workflows section of your Bubble editor, click 'Add workflow' and name it `printful_webhook`. Add the 'Detect request data' trigger — Bubble will listen for the next incoming POST request to map the schema. In Printful, go to your store settings → Webhooks. Click 'Add webhook,' paste your Bubble endpoint URL (`https://yourapp.bubbleapps.io/api/1.1/wf/printful_webhook`), and select the events: 'Package shipped' and 'Order updated' (at minimum). Save the webhook. Printful sends a test webhook ping immediately. In Bubble's Backend Workflows Detect Data mode, it will capture this payload. The Printful webhook payload contains `type` (event type string), `data.order` (full order object including `status`, `shipment_tracking`, and `recipient`), and `data.shipment` (for shipping events, includes `tracking_number`, `tracking_url`, `carrier`, `service`). Add actions to your Backend Workflow: use 'Make changes to Thing' to update your Order Data Thing (find the order by Printful's order ID), set the status field, and if the event type is 'package_shipped,' save the tracking number and URL. Finally, trigger any customer notification workflows (email via SendGrid, or push notification). RapidDev's team routinely builds this exact fulfillment pipeline — for complex multi-variant setups, reach out at rapidevelopers.com/contact.
1// Bubble Backend Workflow URL for Printful webhooks:2https://yourapp.bubbleapps.io/api/1.1/wf/printful_webhook34// Printful webhook payload (package_shipped event):5// {6// "type": "package_shipped",7// "created": 1737982853,8// "retries": 0,9// "store": 123456,10// "data": {11// "shipment": {12// "id": 9876543,13// "status": "shipped",14// "tracking_number": "1Z999AA10123456784",15// "tracking_url": "https://www.ups.com/track?tracknum=...",16// "created": 1737982850,17// "ship_date": "2026-01-27",18// "shipped_at": 1737982850,19// "reshipment": false,20// "carrier": "UPS",21// "service": "UPS Ground"22// },23// "order": {24// "id": 111222333,25// "external_id": "BUBBLE-ORDER-456",26// "status": "fulfilled",27// "shipping": "STANDARD"28// }29// }30// }3132// NOTE: 'created' timestamps in Printful webhooks are Unix seconds33// To display as a readable date in Bubble, store as text and display34// or multiply by 1000 in a Run JavaScript action to convert to millisecondsPro tip: Printful retries failed webhook deliveries several times over 24 hours if your endpoint returns a non-200 status. Make sure your Bubble Backend Workflow always returns a 200 response quickly — add a 'Return data from API' action at the end with a simple `{ "received": true }` response body. Process heavy operations (sending emails, updating multiple records) asynchronously after responding.
Expected result: Your Bubble Backend Workflow endpoint is registered in Printful's webhook settings. When a test event fires from Printful, the workflow triggers and the detected fields (type, data.order.id, data.shipment.tracking_number, etc.) appear in the Backend Workflow logs. The Order Data Thing is updated with the tracking number and new status.
Common use cases
Custom print-on-demand storefront with live product catalog
Build a Bubble storefront that dynamically loads your Printful sync products — T-shirts, hoodies, phone cases — with variant selectors (size, color), mockup images, and prices pulled live from Printful's API. When a customer selects a product and checks out via Stripe, a Bubble workflow automatically places the Printful order, triggering production and shipping without any manual intervention.
On the Products page, bind a Repeating Group to the Printful API Connector GET /sync/products call and display each product's name, thumbnail_url, and retail_price. When a user clicks a product, load its variants from GET /sync/products/{id} and show a size/color dropdown. On checkout button click, run the Stripe payment workflow, and on payment confirmed, trigger the Printful POST /orders call with recipient address and selected variant.
Copy this prompt to try it in Bubble
Order status tracking dashboard for customers
Build a customer-facing dashboard that shows real-time Printful order status — in production, shipped, delivered — by storing order IDs from Printful in a Bubble Data Thing and polling the orders endpoint or receiving webhook updates. Customers see tracking numbers and carrier links without needing access to Printful's own dashboard.
When a Printful order is placed, save the Printful order_id to the customer's Order Data Thing in Bubble. On the customer's Order History page, for each order, make a GET /orders/{id} call to show current_status, shipment_tracking (tracking_number and tracking_url), and estimated_delivery_date from the Printful response.
Copy this prompt to try it in Bubble
Real-time shipping cost estimator before checkout
Show customers exact Printful shipping costs before they enter payment details by calling the Printful shipping rates endpoint with their address and selected product variants. Display carrier options and estimated delivery times, letting customers choose their preferred shipping method before confirming the Stripe payment.
When a customer enters their shipping address on the cart page, trigger a POST to Printful's /shipping/rates with recipient address and items array from the cart Data Thing. Display each returned rate option (name, rate, min_delivery_date, max_delivery_date) in a radio button list and save the selected service name to the customer's session state for use in the order POST.
Copy this prompt to try it in Bubble
Troubleshooting
Bubble API Connector returns empty data even though the Initialize call succeeds with a 200 response
Cause: Printful wraps all API responses in a `result` key: `{ "code": 200, "result": { ... } }`. If Bubble's Repeating Group data source is mapped directly to the top-level response fields (not `result.xxx`), all bindings show empty — the actual data is one level deeper.
Solution: In the API Connector Initialize call response view, expand the `result` node to see the actual product or order fields. When binding a Repeating Group's data source, select the `result` array specifically (not the top-level response). For Repeating Groups, set the List display to `result` and each cell's data to the items inside `result`. All field bindings in cells must reference fields inside the `result` object.
Printful order is created but with an incorrect or null ship date — date fields in Bubble show 'Invalid date'
Cause: Printful uses Unix timestamps in seconds (e.g., `1737982853`) for date fields like `created` and `updated`. Bubble's Date field type expects ISO 8601 format (e.g., `2026-01-27T14:32:33Z`). Storing a Unix integer in a Bubble Date field results in a null or misinterpreted value.
Solution: Store Printful date values as text fields in Bubble instead of Date fields, or use a 'Run JavaScript' action with the Toolbox plugin to convert the timestamp: `new Date(timestamp * 1000).toISOString()` converts Unix seconds to ISO 8601. For display purposes, you can show the text value directly without conversion. For filtering and sorting by date, convert and store as ISO strings.
1// Toolbox plugin → Run JavaScript action2// Convert Printful Unix timestamp to ISO string for Bubble Date field:3new Date(bubble_fn_unix_timestamp * 1000).toISOString()4// Returns: '2026-01-27T14:32:33.000Z''There was an issue setting up your call' when initializing the POST /orders call
Cause: Bubble's Initialize call for POST endpoints requires a real successful response — not an error. If the test order body is missing required fields (recipient name, address, or at least one valid sync_variant_id), Printful returns a 400 error and Bubble cannot detect the response schema.
Solution: Before clicking Initialize on the POST /orders call, ensure all required body parameters have valid test values filled in: recipient.name, recipient.address1, recipient.city, recipient.state_code (2-letter), recipient.country_code (2-letter ISO), recipient.zip, and at least one item with a real sync_variant_id from your Printful catalog and quantity=1. Add 'draft: true' to the test body to prevent Printful from actually processing the order.
Printful webhooks are not arriving at the Bubble Backend Workflow endpoint
Cause: The Bubble Workflow API is not enabled, the Backend Workflow URL has the wrong path, or the Bubble app is in development mode (not deployed). Backend Workflow endpoints only respond to external requests when the app is live.
Solution: Go to Settings → API → verify 'This app exposes a Workflow API' is checked. Deploy your Bubble app (click the Deploy button). In Printful's webhook settings, verify the URL matches exactly: `https://yourapp.bubbleapps.io/api/1.1/wf/printful_webhook` — no trailing slash, no /initialize suffix. Click 'Send test' in Printful's webhook settings and watch the Bubble Logs → Backend Workflow logs for the incoming request.
Best practices
- Always trigger the Printful POST /orders call from a Backend Workflow, not a client-side Bubble action. Printful charges your wallet immediately on order creation — if a client-side action fails mid-execution (browser closed, network interruption), you could end up with a Stripe payment collected but no Printful order placed.
- Use Printful's `draft: true` flag in the order body during development and testing. Draft orders appear in Printful's dashboard but are not sent to production. Remove this flag only when you are ready to go live — and test with a real order to verify the full flow works.
- Never store Printful's API token in a Bubble database field, custom state, or page URL parameter. The token goes only in the API Connector's shared Authorization header with the Private checkbox enabled. If you need to rotate a token, update it in one place in the API Connector settings.
- Add Privacy rules to any Bubble Data Things that store customer shipping addresses (recipient name, address, email). Printful order records contain personally identifiable information — set Data tab → Privacy → Everyone else to deny access on these types.
- For stores with many products, implement pagination using the `offset` parameter in the GET /sync/products call. Printful's API returns a maximum of 100 products per page. Use the `paging.total` value from the response to calculate the total number of pages and show a 'Load More' button or paginator in Bubble.
- Monitor Workload Units in Bubble's Logs tab when your store gets traffic. Each GET /sync/products call, product detail call, and shipping rate call consumes WU. Cache frequently viewed product data in Bubble Data Things (sync product catalog nightly) to reduce per-pageload API calls and WU cost.
- Register a webhook for the `package_shipped` event at minimum — this is the most important customer-facing event. Sending tracking information to customers when Printful ships reduces support tickets and improves customer satisfaction. Pair it with an email notification via SendGrid or Mailchimp triggered from your Backend Workflow.
Alternatives
Shippo handles shipping label generation and multi-carrier rate comparison for products you already have in stock — it does not do printing or fulfillment. Use Printful when your products are custom-printed on-demand; use Shippo when you store physical inventory and need to generate shipping labels for self-fulfilled orders.
WooCommerce is a full e-commerce platform (product management, inventory, checkout) that can be paired with Printful as a fulfillment provider. In a Bubble context, WooCommerce replaces Bubble as the store backend. Use Printful directly with Bubble if you want Bubble to be your entire storefront; use WooCommerce if you need WooCommerce's native product management alongside Printful fulfillment.
Stripe handles payment processing and is typically used together with Printful, not instead of it. Stripe collects customer payment; Printful fulfills the order. Both integrations are required for a complete Bubble POD storefront — configure Stripe first, then trigger Printful order placement from your Stripe payment-confirmed workflow.
Frequently asked questions
Will Printful charge me when I run Initialize calls or test API calls in Bubble?
No — GET calls (product catalog, shipping rates, order lookup) never charge your Printful wallet. Only POST /orders creates a real chargeable order. Even POST /orders only charges if you do not include `draft: true` in the body. During testing, always include `draft: true` in the body of any POST /orders call to create a draft order that does not trigger production or billing.
Do I need a paid Bubble plan to integrate with Printful?
Not for the core integration. Fetching the product catalog, estimating shipping, and placing orders are all API Connector calls that work on any Bubble plan including Free. The one feature that requires a paid Bubble plan (Starter or above) is the inbound webhook Backend Workflow — receiving Printful's package_shipped and order_updated events. On the free plan, you can poll the GET /orders endpoint manually instead, but you won't receive real-time push notifications.
How do I display mockup images for product variants in Bubble?
From the GET /sync/products/{id} call, each sync_variant includes a 'files' array. Find the file with `type: 'preview'` — its `preview_url` field is a direct image URL you can display in Bubble's Image element. Some variants also have `type: 'front'` and `type: 'back'` files for flat-lay views. In a Bubble Repeating Group for variants, bind the Image element's source to `current cell's result.sync_variants[n].files[0].preview_url` (use the file with type='preview').
Can I use Printful with a Bubble app on a custom domain?
Yes. Printful's API does not care about your Bubble app's domain — the API calls come from Bubble's servers, not your domain. For webhooks, register the Bubble Backend Workflow URL (`yourapp.bubbleapps.io` or your custom domain if configured) in Printful's webhook settings. The webhook endpoint URL remains the Bubble API URL format regardless of your custom domain.
How do I handle multiple products in a single Printful order (cart with several items)?
The POST /orders items array supports multiple objects. Build a Bubble Data Thing called 'CartItem' with fields for sync_variant_id and quantity. When a customer adds products to their cart, create CartItem records. When placing the Printful order, use a dynamic text expression to build the items array JSON by listing all CartItem records for the current user. Alternatively, use Bubble's 'List of texts' operator to construct the JSON array from the cart items and include it in the raw JSON body.
What happens if Printful cannot fulfill an order I placed from Bubble?
Printful sends an order status webhook with `type: order_updated` and a status of 'failed' or 'canceled.' Your Backend Workflow should handle this event by updating the Order status in Bubble and triggering a notification to your support team and the affected customer. You will also receive an email from Printful. The customer's Stripe payment is not automatically refunded — you must issue the refund manually via Stripe or through a Stripe refund API call from a Bubble workflow.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation