Bubble.io Plugin Development — Project Rules
Project identity
This is a Bubble.io plugin development boilerplate. It provides the folder structure, coding conventions, and tooling for building plugins that run inside the Bubble.io no-code platform.
Plugins are deployed by copying code into the Bubble Plugin Editor — no build step, no npm publish.
Project structure
project-root/
actions/
client/ # Client-side workflow actions
<action-name>/
action-setup.md
client.js
params.json # Optional: parameter definitions
server/ # Server-side actions (runs on Bubble's Node.js server)
<action-name>/
action-setup.md
server.js
elements/ # Visual plugin elements
<element-name>/
element-setup.md
initialize.js # Runs once on element load
update.js # Runs on every property change + data load
preview.js # Renders placeholder in Bubble Editor
header.html # <head> content: CDN links, external scripts
actions/ # Element-specific workflow actions
<action>.js
eslint.config.mjs # ESLint flat config
package.json # ESLint scripts and dependencies
README.md
Key architectural fact
Each local file maps 1:1 to a text field in the Bubble Plugin Editor:
| Local file |
Bubble Editor field |
initialize.js |
Function: initialize |
update.js |
Function: update |
preview.js |
Function: preview |
header.html |
Element Header |
actions/<name>.js |
Element Action code |
server/<name>/server.js |
Server-Side Action code |
styles.css |
Shared/Element Header (wrap in <style> tags) |
Code quality expectations
When generating or editing any code in this project, follow these rules unconditionally.
Well-formatted, readable code
All code must be clean, consistently formatted, and easy to scan. This means:
- Logical sections separated by blank lines — group related statements together (data loading, guards, rendering, event binding).
- Descriptive variable names — avoid single-letter or cryptic abbreviations (
container not c, itemCount not ic).
- Consistent indentation — 2-space indent for all JS; match surrounding code if editing an existing file.
- Section banners for
update.js — use comment blocks (// === SECTION ===) to delimit lifecycle phases (data loading → guard → change detection → cleanup → render).
- One concern per function — extract helpers for any logic longer than ~10 lines; define helpers inside the wrapper function to avoid global leaks.
Inline documentation
Every non-trivial block of code must include an inline comment explaining why it exists, not just what it does. Specifically:
- Data loading — explain what each
properties.* field contains and why it is loaded first.
- Guards / early returns — explain the condition being checked and what would happen without the guard.
- DOM mutations — explain the structure being built and any Bubble-specific constraints (e.g., why we use
instance.canvas instead of document.body).
- Event listeners — explain the namespace convention and why previous listeners are removed.
- Workarounds — any Bubble quirk or browser compat hack must have a comment linking to the reason.
JSDoc comments
All functions (wrappers and helpers) must have JSDoc blocks. Follow the rules in documentation.md Section 1. Summary:
- Wrapper functions (
initialize, update, preview, actions) — include a top-level @description summarising the function's purpose, followed by @param tags for each argument (instance, properties, context).
- Helper functions —
@param, @returns, and a one-line description.
- Placement — JSDoc goes inside the wrapper, not above it (the wrapper line is stripped when pasting into Bubble).
Example (initialize wrapper):
let initialize = function(instance, context) {
/**
* @description One-time setup for the PLUGIN_PREFIX element.
* Creates the root DOM container, generates a unique event namespace,
* and initialises default exposed states.
*
* @param {object} instance - Bubble element instance (canvas, data, publishState, etc.)
* @param {object} context - Bubble context (keys, currentUser, etc.)
*/
// ... implementation ...
};
Debug logging (verbose_logging)
When scaffolding a new element or action from scratch, ask the user once:
"Should this component include a verbose_logging toggle? This adds a boolean field in the Bubble Plugin Editor that gates all console.log output at runtime."
Do not ask on edits, reviews, refactors, or bug fixes — only on new scaffolds.
If the user accepts:
- Add a boolean field called
verbose_logging to the element or action configuration in the Bubble Plugin Editor and document it in the relevant setup file.
- Gate all
console.log calls behind properties.verbose_logging:
if (properties.verbose_logging) {
console.log('[PLUGIN_PREFIX] update called', { properties });
}
- Log placement — add gated log statements at:
- Entry point of
update.js, client actions, and server actions
- After data loading completes
- Before and after external API calls (server actions)
console.error() in catch blocks is always unconditional — never gate error logging behind the verbose flag.
initialize.js does not receive properties — verbose logging is unavailable. Use a plain console.log only for temporary init-time debugging; remove before production.
preview.js and header.html run in the editor only — verbose logging does not apply.
If the user declines, omit all console.log statements. console.error() in catch blocks remains unconditionally.
Critical pitfalls — always keep in mind
These are the highest-consequence rules. Violating any of these causes hard-to-debug failures:
- Never catch the
'not ready' exception — Bubble uses it as control flow for data loading. If you must use try/catch, re-throw when err.message === 'not ready'.
- Load all data at the TOP of the function — before any DOM mutations. Bubble re-runs the entire function from the start when data arrives.
- Never append to
document.body — use instance.canvas for all visual output.
- Never put API keys in client-side code — use server-side actions with
context.keys.
- Copy only the function BODY to the Bubble Plugin Editor — not the wrapper.
- Prefix all CSS classes (e.g.,
myPlugin-root) — avoid collisions with the host app.
- SSA in v4 must be
async — use await on .get(), .length(), and fetch().
- Headers only support
<script>, <meta>, <link> — anything else gets auto-moved to <body>.
- Do NOT use
$(document).ready() inside plugin functions — it breaks Bubble's dependency detection.
Which reference to load
Do not preload all files. Determine the task type, then load only the relevant reference:
- Determine the task:
- Writing/reviewing element runtime code (
initialize.js, update.js, preview.js, header.html)? → Load bubble-platform.md
- Need
instance/properties/context API details, or v4 migration? → Load bubble-api.md
- Working on actions (client-side or server-side)? → Load actions-guide.md
- Writing, reviewing, or refactoring any JavaScript? → Load code-standards.md
- Writing docs, setup files, or user-facing text? → Load documentation.md
- Multiple concerns? → Load the most relevant file first, add others only if needed.
| File |
Load when... |
| bubble-platform.md |
Element lifecycle, DOM/canvas, data loading, headers, preview, events, debugging, hard limits. |
| bubble-api.md |
instance, properties, context API reference. BubbleThing/BubbleList types. Custom data types / API Connector App Types. Plugin API v4 migration. |
| actions-guide.md |
Client vs server actions. When to use which. SSA Node modules, return values, option sets. |
| code-standards.md |
ESLint config, syntax rules, security, performance, error handling. |
| documentation.md |
JSDoc, setup files, marketplace descriptions, field tooltips, changelog, publishing. |
Starter templates
When scaffolding a new element or action, copy the relevant template from assets/templates/:
| Template |
Use for |
initialize.js |
New element — container setup, instance.data, event namespace |
update.js |
New element — data-first pattern, change detection, namespaced listeners |
preview.js |
New element — editor placeholder with responsive sizing |
header.html |
New element — idempotent <script> loading |
client-action.js |
New client-side action |
server-action.js |
New server-side action (v4 async/await) |
General expectations
- State reasoning. When recommending a change, explain why — do not just state the rule.
- Preserve existing patterns. Before introducing a new pattern, check if the codebase already uses a convention for the same concern.
- No unnecessary files. Do not create files unless the task requires it. Prefer editing existing files.
- Linting is enforced via ESLint. Configuration lives in
eslint.config.mjs (flat config format). VS Code auto-fixes on save via .vscode/settings.json (source.fixAll.eslint). Do not introduce a second formatter.
1---2name: bubble-io-plugins3description: Bubble.io plugin development rules, API reference, and coding standards. Use when working on any task in this repo: writing, reviewing, refactoring, or creating initialize.js, update.js, preview.js, header.html, element actions, client-side actions, server-side actions (SSA), Plugin API v4 async/await code, JSDoc, setup files, README, CHANGELOG, marketplace descriptions, or field tooltips. Also use for security audits, code review, debugging, and publishing plugins. Covers instance/properties/context objects, BubbleThing/BubbleList interfaces, data loading suspension, DOM/canvas rules, element vs shared headers, exposed states, event handling, ESLint standards, and Bubble hard limits.4---56# Bubble.io Plugin Development — Project Rules78## Project identity910This is a **Bubble.io plugin development boilerplate**. It provides the folder structure, coding conventions, and tooling for building plugins that run inside the Bubble.io no-code platform.1112Plugins are deployed by **copying code into the Bubble Plugin Editor** — no build step, no npm publish.1314## Project structure1516```17project-root/18 actions/19 client/ # Client-side workflow actions20 <action-name>/21 action-setup.md22 client.js23 params.json # Optional: parameter definitions24 server/ # Server-side actions (runs on Bubble's Node.js server)25 <action-name>/26 action-setup.md27 server.js28 elements/ # Visual plugin elements29 <element-name>/30 element-setup.md31 initialize.js # Runs once on element load32 update.js # Runs on every property change + data load33 preview.js # Renders placeholder in Bubble Editor34 header.html # <head> content: CDN links, external scripts35 actions/ # Element-specific workflow actions36 <action>.js37 eslint.config.mjs # ESLint flat config38 package.json # ESLint scripts and dependencies39 README.md40```4142### Key architectural fact4344Each local file maps 1:1 to a text field in the Bubble Plugin Editor:4546| Local file | Bubble Editor field |47|---|---|48| `initialize.js` | Function: initialize |49| `update.js` | Function: update |50| `preview.js` | Function: preview |51| `header.html` | Element Header |52| `actions/<name>.js` | Element Action code |53| `server/<name>/server.js` | Server-Side Action code |54| `styles.css` | Shared/Element Header (wrap in `<style>` tags) |5556## Code quality expectations5758When generating or editing any code in this project, follow these rules unconditionally.5960### Well-formatted, readable code6162All code must be **clean, consistently formatted, and easy to scan**. This means:6364- **Logical sections separated by blank lines** — group related statements together (data loading, guards, rendering, event binding).65- **Descriptive variable names** — avoid single-letter or cryptic abbreviations (`container` not `c`, `itemCount` not `ic`).66- **Consistent indentation** — 2-space indent for all JS; match surrounding code if editing an existing file.67- **Section banners for `update.js`** — use comment blocks (`// === SECTION ===`) to delimit lifecycle phases (data loading → guard → change detection → cleanup → render).68- **One concern per function** — extract helpers for any logic longer than ~10 lines; define helpers *inside* the wrapper function to avoid global leaks.6970### Inline documentation7172Every non-trivial block of code must include an inline comment explaining **why** it exists, not just what it does. Specifically:7374- **Data loading** — explain what each `properties.*` field contains and why it is loaded first.75- **Guards / early returns** — explain the condition being checked and what would happen without the guard.76- **DOM mutations** — explain the structure being built and any Bubble-specific constraints (e.g., why we use `instance.canvas` instead of `document.body`).77- **Event listeners** — explain the namespace convention and why previous listeners are removed.78- **Workarounds** — any Bubble quirk or browser compat hack must have a comment linking to the reason.7980### JSDoc comments8182All functions (wrappers and helpers) must have JSDoc blocks. Follow the rules in [documentation.md](references/documentation.md) Section 1. Summary:8384- **Wrapper functions** (`initialize`, `update`, `preview`, actions) — include a top-level `@description` summarising the function's purpose, followed by `@param` tags for each argument (`instance`, `properties`, `context`).85- **Helper functions** — `@param`, `@returns`, and a one-line description.86- **Placement** — JSDoc goes **inside** the wrapper, not above it (the wrapper line is stripped when pasting into Bubble).8788Example (initialize wrapper):8990```javascript91let initialize = function(instance, context) {92 /**93 * @description One-time setup for the PLUGIN_PREFIX element.94 * Creates the root DOM container, generates a unique event namespace,95 * and initialises default exposed states.96 *97 * @param {object} instance - Bubble element instance (canvas, data, publishState, etc.)98 * @param {object} context - Bubble context (keys, currentUser, etc.)99 */100101 // ... implementation ...102};103```104105### Debug logging (`verbose_logging`)106107**When scaffolding a new element or action from scratch**, ask the user once:108109> "Should this component include a `verbose_logging` toggle? This adds a boolean field in the Bubble Plugin Editor that gates all `console.log` output at runtime."110111Do **not** ask on edits, reviews, refactors, or bug fixes — only on new scaffolds.112113If the user **accepts**:1141151. **Add a boolean field** called `verbose_logging` to the element or action configuration in the Bubble Plugin Editor and document it in the relevant setup file.1162. **Gate all `console.log` calls** behind `properties.verbose_logging`:117118```javascript119if (properties.verbose_logging) {120 console.log('[PLUGIN_PREFIX] update called', { properties });121}122```1231243. **Log placement** — add gated log statements at:125 - Entry point of `update.js`, client actions, and server actions126 - After data loading completes127 - Before and after external API calls (server actions)1284. **`console.error()` in `catch` blocks is always unconditional** — never gate error logging behind the verbose flag.1295. **`initialize.js`** does not receive `properties` — verbose logging is unavailable. Use a plain `console.log` only for temporary init-time debugging; remove before production.1306. **`preview.js` and `header.html`** run in the editor only — verbose logging does not apply.131132If the user **declines**, omit all `console.log` statements. `console.error()` in `catch` blocks remains unconditionally.133134---135136## Critical pitfalls — always keep in mind137138These are the highest-consequence rules. Violating any of these causes hard-to-debug failures:1391401. **Never catch the `'not ready'` exception** — Bubble uses it as control flow for data loading. If you must use `try/catch`, re-throw when `err.message === 'not ready'`.1412. **Load all data at the TOP of the function** — before any DOM mutations. Bubble re-runs the entire function from the start when data arrives.1423. **Never append to `document.body`** — use `instance.canvas` for all visual output.1434. **Never put API keys in client-side code** — use server-side actions with `context.keys`.1445. **Copy only the function BODY** to the Bubble Plugin Editor — not the wrapper.1456. **Prefix all CSS classes** (e.g., `myPlugin-root`) — avoid collisions with the host app.1467. **SSA in v4 must be `async`** — use `await` on `.get()`, `.length()`, and `fetch()`.1478. **Headers only support `<script>`, `<meta>`, `<link>`** — anything else gets auto-moved to `<body>`.1489. **Do NOT use `$(document).ready()`** inside plugin functions — it breaks Bubble's dependency detection.149150## Which reference to load151152**Do not preload all files.** Determine the task type, then load only the relevant reference:1531541. Determine the task:155 - **Writing/reviewing element runtime code** (`initialize.js`, `update.js`, `preview.js`, `header.html`)? → Load [bubble-platform.md](references/bubble-platform.md)156 - **Need `instance`/`properties`/`context` API details, or v4 migration?** → Load [bubble-api.md](references/bubble-api.md)157 - **Working on actions** (client-side or server-side)? → Load [actions-guide.md](references/actions-guide.md)158 - **Writing, reviewing, or refactoring any JavaScript?** → Load [code-standards.md](references/code-standards.md)159 - **Writing docs, setup files, or user-facing text?** → Load [documentation.md](references/documentation.md)160 - **Multiple concerns?** → Load the most relevant file first, add others only if needed.161162| File | Load when... |163|---|---|164| [bubble-platform.md](references/bubble-platform.md) | Element lifecycle, DOM/canvas, data loading, headers, preview, events, debugging, hard limits. |165| [bubble-api.md](references/bubble-api.md) | `instance`, `properties`, `context` API reference. BubbleThing/BubbleList types. Custom data types / API Connector App Types. Plugin API v4 migration. |166| [actions-guide.md](references/actions-guide.md) | Client vs server actions. When to use which. SSA Node modules, return values, option sets. |167| [code-standards.md](references/code-standards.md) | ESLint config, syntax rules, security, performance, error handling. |168| [documentation.md](references/documentation.md) | JSDoc, setup files, marketplace descriptions, field tooltips, changelog, publishing. |169170## Starter templates171172When scaffolding a new element or action, copy the relevant template from `assets/templates/`:173174| Template | Use for |175|---|---|176| `initialize.js` | New element — container setup, `instance.data`, event namespace |177| `update.js` | New element — data-first pattern, change detection, namespaced listeners |178| `preview.js` | New element — editor placeholder with responsive sizing |179| `header.html` | New element — idempotent `<script>` loading |180| `client-action.js` | New client-side action |181| `server-action.js` | New server-side action (v4 async/await) |182183## General expectations1841851. **State reasoning.** When recommending a change, explain *why* — do not just state the rule.1862. **Preserve existing patterns.** Before introducing a new pattern, check if the codebase already uses a convention for the same concern.1873. **No unnecessary files.** Do not create files unless the task requires it. Prefer editing existing files.1884. **Linting is enforced via ESLint.** Configuration lives in `eslint.config.mjs` (flat config format). VS Code auto-fixes on save via `.vscode/settings.json` (`source.fixAll.eslint`). Do not introduce a second formatter.