Learn-skills.dev bubble-io-plugins
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/afaraha8403/bubble-io-plugin-boilerplate/bubble-io-plugins" ~/.claude/skills/neversight-learn-skills-dev-bubble-io-plugins && rm -rf "$T"
data/skills-md/afaraha8403/bubble-io-plugin-boilerplate/bubble-io-plugins/SKILL.mdBubble.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 |
|---|---|
| Function: initialize |
| Function: update |
| Function: preview |
| Element Header |
| Element Action code |
| Server-Side Action code |
| Shared/Element Header (wrap in 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 (
notcontainer
,c
notitemCount
).ic - Consistent indentation — 2-space indent for all JS; match surrounding code if editing an existing file.
- Section banners for
— use comment blocks (update.js
) to delimit lifecycle phases (data loading → guard → change detection → cleanup → render).// === SECTION === - 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
field contains and why it is loaded first.properties.* - 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
instead ofinstance.canvas
).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
, actions) — include a top-levelpreview
summarising the function's purpose, followed by@description
tags for each argument (@param
,instance
,properties
).context - Helper functions —
,@param
, and a one-line description.@returns - 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
)
verbose_loggingWhen scaffolding a new element or action from scratch, ask the user once:
"Should this component include a
toggle? This adds a boolean field in the Bubble Plugin Editor that gates allverbose_loggingoutput at runtime."console.log
Do not ask on edits, reviews, refactors, or bug fixes — only on new scaffolds.
If the user accepts:
- Add a boolean field called
to the element or action configuration in the Bubble Plugin Editor and document it in the relevant setup file.verbose_logging - Gate all
calls behindconsole.log
:properties.verbose_logging
if (properties.verbose_logging) { console.log('[PLUGIN_PREFIX] update called', { properties }); }
- Log placement — add gated log statements at:
- Entry point of
, client actions, and server actionsupdate.js - After data loading completes
- Before and after external API calls (server actions)
- Entry point of
inconsole.error()
blocks is always unconditional — never gate error logging behind the verbose flag.catch
does not receiveinitialize.js
— verbose logging is unavailable. Use a plainproperties
only for temporary init-time debugging; remove before production.console.log
andpreview.js
run in the editor only — verbose logging does not apply.header.html
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
exception — Bubble uses it as control flow for data loading. If you must use'not ready'
, re-throw whentry/catch
.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
— usedocument.body
for all visual output.instance.canvas - 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.,
) — avoid collisions with the host app.myPlugin-root - SSA in v4 must be
— useasync
onawait
,.get()
, and.length()
.fetch() - Headers only support
,<script>
,<meta>
— anything else gets auto-moved to<link>
.<body> - Do NOT use
inside plugin functions — it breaks Bubble's dependency detection.$(document).ready()
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
)? → Load bubble-platform.mdheader.html - Need
/instance
/properties
API details, or v4 migration? → Load bubble-api.mdcontext - 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.
- Writing/reviewing element runtime code (
| File | Load when... |
|---|---|
| bubble-platform.md | Element lifecycle, DOM/canvas, data loading, headers, preview, events, debugging, hard limits. |
| bubble-api.md | , , 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 |
|---|---|
| New element — container setup, , event namespace |
| New element — data-first pattern, change detection, namespaced listeners |
| New element — editor placeholder with responsive sizing |
| New element — idempotent loading |
| New client-side action |
| 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
(flat config format). VS Code auto-fixes on save viaeslint.config.mjs
(.vscode/settings.json
). Do not introduce a second formatter.source.fixAll.eslint