Frappe fixtures
Skill prilk-consulting/frappe-agent-kit/skills/frappe-fixtures
Production-grade Frappe Framework & ERPNext skills, agents, and scaffolding commands for Claude Code
npx -y skills add prilk-consulting/frappe-agent-kit --skill frappe-fixturesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Frappe fixtures, workspaces, and sidebar setup. Use for exporting/syncing fixtures, Workspace + Workspace Sidebar + Desktop Icon configuration, nested-set fixture ordering, and per-environment ID pitfalls that break fresh-site migrations. Not for data-migration patches or patches.txt — use frappe-upgrade.
SKILL.md
6.4 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Fixtures, Workspaces & Sidebars
Usage
Use this skill when:
- Exporting or syncing fixtures (
bench export-fixtures, hooksfixtureslist) - Setting up a Workspace, Workspace Sidebar, or Desktop Icon for an app
- Debugging fresh-site
bench migratecrashes caused by fixture ordering or per-environment links - Deciding between fixtures and install.py for custom fields
Fixture basics
# hooks.py
fixtures = [
{"dt": "Custom Field", "filters": [["module", "=", "My Module"]]},
{"dt": "Property Setter", "filters": [["module", "=", "My Module"]]},
{"dt": "Role", "filters": [["name", "in", ["My Role"]]]},
]
bench --site <site> export-fixtures --app myapp # DB → apps/myapp/myapp/fixtures/*.json
bench --site <site> migrate # JSON → DB (runs sync_fixtures)
bench --site <site> execute frappe.utils.fixtures.sync_fixtures --args "['myapp']" # sync only
- Always filter — an unfiltered
{"dt": "Custom Field"}exports every custom field on the site, including other apps'. Each app owns only its own records. - Use
or_filterswhen records can't be matched by one field:{"dt": "Wiki Document", "or_filters": [...]}. - Fixture sync is upsert-only: deleting a record from the JSON does not delete it from sites that already have it. Removals need a patch.
- Re-export after every UI-side change, and diff the export before committing —
export-fixturesrewrites whole files and happily captures unrelated records that match your filter. - For content managed live in the UI by end users (website pages, wiki content), consider NOT shipping fixtures at all — a fixture sync on deploy will overwrite their edits. Fixtures fit configuration, not user-owned content.
Workspace + Sidebar + Desktop Icon
CRITICAL naming rule
- Workspace Sidebar
nameMUST differ from the module name - Workspace Sidebar
modulefield MUST equal the module name (this prevents auto-gen) - Example: module="Logistics" → sidebar
name="Logistics Hub", sidebarmodule="Logistics"
If name matches module, Frappe's auto_generate_sidebar_from_module() creates a cached auto-generated sidebar (top 3 DocTypes only) that overrides yours.
# hooks.py
fixtures = [
{"dt": "Workspace", "filters": [["name", "in", ["MyWorkspace"]]]},
{"dt": "Workspace Sidebar", "filters": [["name", "in", ["My Sidebar"]]]},
{"dt": "Desktop Icon", "filters": [["name", "in", ["My Sidebar"]]]},
]
// fixtures/workspace_sidebar.json (minimal)
[{
"doctype": "Workspace Sidebar", "name": "My Sidebar", "module": "MyModule",
"title": "My Sidebar", "header_icon": "bot-message-square", "standard": 1,
"items": [
{"label": "Home", "link_to": "MyWorkspace", "link_type": "Workspace", "type": "Link"},
{"label": "My DocType", "link_to": "My DocType", "link_type": "DocType", "type": "Link", "icon": "list"}
]
}]
- Desktop Icon
link_toandsidebarreference the sidebar name. - Icon SVG: 118x118px, colored background + white icon, in
public/images/. - Sidebar item icons: pick from
frappe/public/icons/lucide/icons.svg. - After syncing:
bench --site <site> clear-cache(sidebars are cached).
Nested-set fixture ordering gotcha
bench export-fixtures writes records sorted alphabetically by name. For nested-set doctypes (tree doctypes: Item Group, Customer Group, Account, Wiki Document, BOM trees, …) this can put children before their parents in the JSON — random hash names sort unpredictably.
On a fresh site, bench migrate hits the child row first, calls update_add_node, looks up the parent's lft/rgt — and the parent doesn't exist yet:
File ".../frappe/utils/nestedset.py", line 77, in update_add_node
left, right = frappe.db.get_value(doctype, {"name": parent}, ["lft", "rgt"], for_update=True)
TypeError: cannot unpack non-iterable NoneType object
Fix: topologically re-sort the JSON after every export so each record appears only after its parent. Run the bundled script (in this skill's scripts/ directory):
python3 scripts/sort_nested_fixtures.py apps/<app>/<app>/fixtures/<doctype>.json <parent_fieldname>
# e.g. python3 scripts/sort_nested_fixtures.py apps/myapp/myapp/fixtures/item_group.json parent_item_group
It re-sorts by tree depth in place (idempotent; exit 2 on parent cycles). Run it as a post-export step after every bench export-fixtures of a tree doctype. Fixture order is per-file; fixture files import alphabetically by filename, so make sure parent-doctype fixtures sort before child-doctype fixtures if they're linked.
This gotcha never shows on the dev site where the records already exist — only on fresh installs / first bench migrate of a downstream bench. Test fixtures on a throwaway site before shipping.
Per-environment ID gotcha
Fixtures that contain links to records that are generated per-environment (revision IDs, version rows, file attachments, auto-named children that aren't themselves exported) will migrate fine on the source site and crash on every other site with LinkValidationError: Could not find <DocType>.
Before committing an export, null out or strip any field whose value is an autoname/hash that the fixture set itself does not include. Typical offenders: "current revision" pointers, "latest version" links, cached file URLs pointing at /private/files/.
# post-export scrub example
for d in docs:
d["main_revision"] = None # per-environment pointer; rebuilt on first save
Custom fields: fixtures vs install.py
Two valid approaches — pick ONE per app, don't mix:
| Approach | Pro | Con |
|---|---|---|
install.py + create_custom_fields() in after_install/after_migrate | Code-reviewed dicts, no export step, idempotent | Fields not visible in fixture diffs |
fixtures = [{"dt": "Custom Field", "filters": [["module", "=", "X"]]}] | UI-first workflow | Export captures noise; needs module set on every field |
The install.py approach is generally preferred for apps that extend other apps' DocTypes — see the frappe-dev skill.
What ships with it: 1 file
1.9 KB alongside SKILL.md, 1 of them executable
scripts/
- sort_nested_fixtures.pyruns1.9 KB