Editor Demo:
https://bubble.io/page?id=blur-apps-playground&test_plugin=1773552779309x662951808931725300_current&tab=Design&name=better-pdf-table&type=page&type_id=column_config
Run Mode Demo:
https://blur-apps-playground.bubbleapps.io/version-test/better-pdf-tableHOW IT WORKS
------------
1. Place the element on your page (it is invisible to users)
2. Set column headers and bind your data
3. Trigger the "GeneratePDF" action from a button click
4. PDF downloads instantly to the user's device
========================================
SIMPLE MODE (up to 10 columns)
========================================
STEP 1 -- Place the Element
Drag "Better Table PDF" onto your page.
It shows "PDF Generator Ready" at runtime but is invisible to your users.
Size does not matter.
STEP 2 -- Set Document Properties
Report title ......... Text shown centered at top (e.g. "Sales Report")
Report subtitle ...... Smaller text below title (e.g. "Q1 2026")
File name ............ Download filename, .pdf is added automatically
Orientation .......... Portrait or Landscape
Page size ............ A4, Letter, or Legal
Table theme .......... Striped (alternating rows), Grid (all borders), or Plain
STEP 3 -- Define Your Columns
For EACH column you need, fill in two fields:
Column N header = The column title text
Column N data = A LIST OF TEXT from your database
>>> IMPORTANT: Column data must be a LIST OF TEXT.
Use "Search for [Type]'s [field]" to get a list.
If your field is not text, append ":formatted as text"
so Bubble returns it as a text list.
EXAMPLE -- 3-column report:
Column 1 header: Customer
Column 1 data: Search for Sales --> each item's Customer Name
Column 2 header: Amount
Column 2 data: Search for Sales --> each item's Amount:formatted as $#,##0
Column 3 header: Date
Column 3 data: Search for Sales --> each item's Created Date:formatted as MM/DD/YYYY
- Columns without a header are ignored
- Leave columns 4-10 empty if you only need fewer
- All columns are CENTER-ALIGNED by default
- Use the "Header alignment (all)" dropdown to override all header alignment
- For per-column alignment control, use Advanced Mode instead
STEP 4 -- Style the Table (all optional, sensible defaults provided)
Header background color .... dark blue (#2c3e50)
Header text color .......... white (#ffffff)
Header font size ........... 11
Body font size ............. 10
Alternate row color ........ light gray (#f2f2f2) -- used by Striped theme
Body text color ............ dark gray (#333333)
Cell padding ............... 5
Margins (top/right/bottom/left) ... 15mm each
Show page numbers .......... checked -- adds "Page 1 of 3" at bottom
Show date on report ........ unchecked -- adds generation date at top-right
STEP 5 -- Page Breaks (optional)
AUTO-PAGINATION:
Set "Rows per page" to a number (e.g. 25).
A new page starts every 25 rows.
MANUAL PAGE BREAKS:
Set "Page break marker" to a string (default: ---BREAK---)
In your Column 1 data list, insert this exact string where you want a break.
That row is skipped and a new page starts.
How to insert in Bubble:
Use :merged with on your data list:
Search for Sales:each item's Name :merged with "---BREAK---" :merged with Search for Returns:each item's Name
You can use BOTH auto-pagination and manual breaks together.
STEP 6 -- Trigger PDF Generation
DOWNLOAD TO USER'S DEVICE:
Button click workflow -->
Element Actions --> GeneratePDF (select your Better Table PDF element)
UPLOAD TO BUBBLE STORAGE:
Button click workflow -->
Element Actions --> GenerateAndUpload (select your Better Table PDF element)
Then use the "pdf_uploaded" event to get the URL:
When Better Table PDF's pdf_uploaded -->
Make changes to [Thing] --> PDF field = Better Table PDF's pdf_url
========================================
ADVANCED MODE (unlimited columns, full control)
========================================
Use this when you need:
- More than 10 columns
- Custom column widths (in mm)
- Different alignment for headers vs body per column
- Columns you can show/hide
- Programmatic/dynamic table configuration
SETUP:
1. Check "Use advanced mode (JSON)" on the element
2. Provide "Advanced: Config JSON" -- a text value with your config
3. Provide "Advanced: Content JSON" -- a text value with your row data
4. Trigger GeneratePDF or GenerateAndUpload (same as Simple Mode)
CONFIG JSON EXAMPLE:
{
"orientation": "landscape",
"pageSize": "A4",
"title": "Inventory Report",
"subtitle": "March 2026",
"theme": "grid",
"rowsPerPage": 30,
"tableConfig": [
{ "id": "sku", "title": "SKU", "align": "left", "visible": true },
{ "id": "name", "title": "Product Name", "align": "left", "width": 60, "visible": true },
{ "id": "qty", "title": "Qty", "align": "right", "width": 20, "visible": true },
{ "id": "price", "title": "Price", "align": "right", "width": 25, "visible": true }
],
"styles": {
"headerBgColor": [41, 128, 185],
"headerTextColor": [255, 255, 255],
"headerFontSize": 11,
"bodyFontSize": 10,
"altRowColor": [235, 245, 255],
"cellPadding": 4
}
}
CONTENT JSON EXAMPLE:
[
{ "sku": "A001", "name": "Widget", "qty": "150", "price": "$12.50" },
{ "sku": "A002", "name": "Gadget", "qty": "75", "price": "$24.00" },
{ "sku": "B001", "name": "Sprocket", "qty": "200", "price": "$8.75" }
]
>>> The "id" in each tableConfig column must match the keys in your content objects.
TABLECONFIG COLUMN OPTIONS:
id ............. (required) key that matches your content JSON objects
title .......... column header text
align .......... "left", "center", or "right" -- body cell alignment
headerAlign .... "left", "center", or "right" -- header-only alignment (optional)
width .......... fixed width in mm (optional, auto if omitted)
visible ........ true or false -- set false to hide a column
STYLES OPTIONS (colors are [R, G, B] arrays, 0-255):
headerBgColor ...... header background, e.g. [44, 62, 80]
headerTextColor .... header text, e.g. [255, 255, 255]
headerFontSize ..... e.g. 11
bodyFontSize ....... e.g. 10
bodyTextColor ...... body text, e.g. [51, 51, 51]
altRowColor ........ alternate row background, e.g. [242, 242, 242]
cellPadding ........ e.g. 5
========================================
STATES, EVENTS, AND ACTIONS
========================================
ELEMENT ACTIONS (use in workflows):
GeneratePDF ........... builds and downloads PDF to user's device
GenerateAndUpload ..... builds and uploads PDF to Bubble storage
Reset ................. clears all output states
STATES (read these in conditions and workflows):
pdf_url ........... Bubble file URL after upload (use with GenerateAndUpload)
is_generating ..... yes while PDF is being built
is_uploading ...... yes while uploading to Bubble storage
error_message ..... text description if something goes wrong
page_count ........ total pages in the generated PDF
row_count ......... total data rows processed
is_ready .......... yes when the element is loaded and ready
EVENTS (trigger workflows from these):
pdf_generated ..... fires when PDF is built (before download/upload)
pdf_downloaded .... fires after PDF is downloaded to user's device
pdf_uploaded ...... fires after PDF is uploaded to Bubble storage
error_occurred .... fires when generation or upload fails
========================================
COMMON PATTERNS
========================================
SHOW LOADING STATE:
Add a condition on your download button:
When Better Table PDF's is_generating is yes -->
Disable the button, change text to "Generating..."
ERROR HANDLING:
Add a workflow:
When Better Table PDF's error_occurred -->
Show alert: Better Table PDF's error_message
SAVE PDF TO DATABASE:
Add a workflow:
When Better Table PDF's pdf_uploaded -->
Make changes to Current User -->
PDF Report = Better Table PDF's pdf_url