Filesystem
When you clone a branch, the CLI writes out a normalized 'shredded' Bubble app. This article is the reference for what lives where, what shape it takes, and what you must not touch.
The filesystem is not a faithful JSON dump of your Bubble app. It is a projection designed for editing: volatile fields are stripped, derived fields are recomputed on apply, and several fields are lifted into directory structure.
Top-level layout
my-app/ # app root (one per Bubble app)
.buildprint/ # internal state — never edit
app.json # { schemaVersion, appId, tokenRef }
remote.git/ # shared bare repo
test/ # branch workspace (git worktree)
.git # gitlink into the shared bare repo
.gitignore # ignores /.buildprint/
app.json # top-level Bubble scalars
settings/
data_types/
option_sets/
styles/
pages/
element-definitions/
mobile-views/
global-elements/
api/The app root is shared across every branch of one Bubble app. Each Bubble branch materializes as a sibling folder inside it. The branch folder name must match the checked-out git branch — see Workspace for the lifecycle and safety rails.
Branch folder contents
Each directory inside a branch folder maps to a Bubble concept:
app.json— top-level scalars for the app (name, plan, defaults). Fields that belong elsewhere have been lifted out.settings/— Bubble settings.settings/client-safe.jsonholds the main settings body;settings/api-connector/<plugin-id>/plugin.jsonandsettings/api-connector/<plugin-id>/calls/<call-id>.jsonhold API connector definitions.data_types/<type-id>/type.json— one folder per data type. Field list is inline in the body.option_sets/<set-id>/option-set.json— one folder per option set. Values are inline, ordered bysort_factor.styles/— styles grouped by element type:styles/<ElementType>/<style-id>.json. Design tokens are lifted into sibling files:styles/tokens.json,styles/defaults.json,styles/fonts.json.pages/<page-id>/— one folder per page. Body filepage.json; elements underelements/; workflows underworkflows/(see below).element-definitions/<id>/— reusable elements. Same shape as pages; body file isreusable.json.mobile-views/<id>/— mobile screens. Same shape as pages.global-elements/<id>/— global elements (app-wide surfaces). Same shape as pages.api/— backend workflows, optionally grouped into Bubble workflow folders (see Workflow folders below).
Pages, reusables, mobile views, global elements
These four directories all use the same structure. The body file at the root of the folder holds the entity's properties; children elements and workflows live in sibling subdirectories.
pages/<page-id>/
page.json # page properties + children manifest
elements/
<element-key>/
element.json # element properties, type, style, states
<child-element-key>/ # nested folder per child element
element.json
<grandchild-key>/
element.json
workflows/
<workflow-id>/
workflow.json # trigger and metadata
actions/
0.json # integer filenames = insertion order
1.json
2.jsonElement folders
The folder name is the element's map key (the key it has inside its parent's
elementsobject), not theidfield inside the body. These can differ; references in other parts of the app use the map key.Child elements live as subfolders of their parent. The tree on disk mirrors the containment tree in Bubble.
Workflows and actions
Each workflow gets its own folder containing
workflow.json(trigger, metadata) and anactions/folder with one file per action.Action filenames are stringified integers —
0.json,1.json,2.json. They encode execution order.Gaps in the sequence are valid and preserved verbatim (some Bubble structures intentionally use sparse integer keys).
Workflow folders
Bubble lets you group workflows into named folders. On disk, a workflow folder is a directory with a config.json alongside the workflows it contains:
api/
<folder-id>/
config.json # { "name": "Billing" }
<wf-id>/
workflow.json
actions/0.json
<wf-id>/ # workflow with no parent folder
workflow.json
actions/0.jsonFolder metadata lives in
config.json. To rename a folder, edit that file.To move a workflow between folders, move the workflow directory itself.
Workflow folders under
api/cannot be nested — one level only. Theapi-folder-flatcheck rule enforces this.Workflow bodies do not carry a
wf_folderfield — the parent folder is derived from the directory tree. Adding that field toworkflow.jsondoes nothing; rearrange the folders instead.
The same structure applies under pages/<page-id>/workflows/, element-definitions/<id>/workflows/, mobile-views/<id>/workflows/, and global-elements/<id>/workflows/.
Styles
Styles are split by element type. Each subdirectory contains one JSON file per style:
styles/
Button/
primary.json
secondary.json
Text/
heading.json
body.json
tokens.json # color_tokens, color_tokens_user
defaults.json # default_styles, default_icon_set
fonts.json # font_tokens, font_tokens_user, fontsThe three lifted files at the top of styles/ were moved out of settings/ so design tokens live in one predictable place.
Canonical JSON form
Every JSON file the CLI writes goes through a single canonical formatter. The rules:
UTF-8 with LF line endings and a trailing newline.
Two-space indentation. One value per line in objects; one element per line in non-trivial arrays.
No tabs, no CR characters, no trailing whitespace.
Non-integer-keyed object keys are alpha-sorted. Integer-keyed maps (workflow
actions, elementstates,TextExpression.entries) preserve insertion order.
Byte-identical output for the same input keeps sync diffs clean and merge conflicts minimal. If you hand-edit a file and the formatting drifts, the canonical-form check rule will flag it — re-run the command that produced the file, or run it through a JSON formatter.
IDs and filenames
Buildprint mints its own IDs.
Fresh IDs come from
buildprint utils generate-ids <n>(one per line, lexicographic order, between 1 and 100 at a time). Call this before creating entities by hand.Existing apps may contain two Bubble ID formats (short-uid and timestamp-random). Readers handle both; writers always emit the canonical Buildprint form.
Keys with the
_bp_orbp-prefix are reserved for the CLI. You'll occasionally see internal marker files and sentinels (for example__bp_dir__.jsonor a__bp_no_type__/folder under styles) — treat them as internal and don't edit them by hand.
Children manifests
Every parent that contains elements keeps a manifest of its children in its body file:
{
"children": ["hdr_0001", "content_0002", "footer_0003"]
}The invariant: the set of child subfolders under elements/ must match the set of keys listed in children, and the order in children is the canonical display order. Dangling entries or orphaned folders are errors flagged by the children-manifest check rule.
Render order is separate: properties.order (flex order) and properties.zindex (stacking) describe CSS concerns and live on individual elements when set. The CLI does not invent values for these — only Bubble does.
What not to edit
.buildprint/— the shared bare git repo and app config. Gitignored in the worktree. Leave it alone..git/internals — treat the worktree like any other git checkout.Lifted fields - anything that was moved out of a body file into directory placement. Writing
properties.wf_folderback intoworkflow.json, ortypeback into a style body, has no effect and may trigger check errors.Volatile fields - if you see a stray
last_change,creation_date,_id,screenshot, oruid_counterfield creep back in from a hand edit, remove it. The CLI strips these on shred and will not regenerate them.Internal marker files - sentinels under
__bp_*folders or files. They exist so the CLI can round-trip edge cases (case-insensitive collisions, styles with no type). Don't author or rename them.
Git model
The branch folder is a real git worktree backed by the bare repo at <app-root>/.buildprint/remote.git/. You can use git status, git diff, git log, git add, and git commit as normal.
Four refs are worth knowing about:
refs/heads/<branch>— your local commits. This is what you edit on.refs/remotes/buildprint/<branch>— the last local commit that was successfully applied to Bubble. Usegit log buildprint/<branch>..HEADto see what's locally ahead.refs/bubble/<branch>— the latest Bubble snapshot (lives in the bare repo).buildprint syncupdates this ref and merges it into your branch.refs/published/<branch>— bookkeeping ref inside the bare repo, mirrored out tobuildprint/<branch>bygit fetch.