MeshWeaver provides four built-in operations for transferring node subtrees between locations, exporting to files, and importing from external sources. They all appear directly in a node's context menu — Move, Copy and Delete in the edit/organize section, Import and Export in the content section (see Node Menu); there is no "Actions" sub-menu.
| Operation | What it does |
|---|---|
| Export | Produces a ZIP archive (manifest.json + files/) of a node and its entire subtree |
| Import | Reads files or a ZIP and creates nodes in the mesh |
| Copy | Duplicates a node and all its descendants to a new namespace |
| Move | Relocates a node and its entire subtree to a new path |
The four node operations and how they relate to a mesh node subtree: Export serialises to files, Import reads files into the mesh, Copy duplicates to a new namespace, and Move relocates the entire subtree.
Export
Export serialises a node and its entire subtree into a self-contained ZIP archive. The archive has exactly two parts:
| Entry | Contents |
|---|---|
manifest.json |
The export-root path plus every descendant MeshNode as full JSON. Code source and Markdown bodies ride inside each node's Content — there are no separate .cs / .md entries. |
files/… |
The raw bytes of every editable content-collection file attached to the exported nodes. |
Subtree traversal uses the same snapshot query Copy uses (path:{root} scope:subtree), and the file reads plus the zipping run on the file-system IIoPool — never on the hub action block.
Permission
Export requires Permission.Export. Nodes the caller lacks it on are silently skipped: partial access yields a subset, no access yields an empty-manifest ZIP. A non-existent path also resolves to an empty-manifest ZIP rather than an error.
Programmatic Export
The programmatic surface is MeshOperations — the same object behind the MCP export / import tools. It is reactive: the observable is cold, so the work runs on Subscribe.
var ops = new MeshOperations(hub); // a facade over the hub, not a DI service
ops.Export("org/acme/project")
.Subscribe(zipBytes => File.WriteAllBytes(outputPath, zipBytes),
ex => logger.LogWarning(ex, "Export failed"));
There is no
IMeshExportService/IMeshImportService— both were deleted in the persistence cull (2026-05-12). The Export node-menu action opensExportLayoutArea, but the view's download path (NodeExportView) has not been rewired onto the new surface and reports so instead of producing a file.MeshOperations.Export/.Importare the working path.
Export–Import Round Trip
Exporting a subtree and re-importing the ZIP restores the original state:
Export org/acme → ZIP (manifest.json + files/)
→ Import the ZIP under a target namespace
→ Node paths are rewritten from the export root to the target;
names, types, content, and content-collection files are preserved
Import
Import reads files from a directory or ZIP and creates nodes in the mesh. Three sources are supported:
| Source | Description |
|---|---|
| Copy from Mesh Node | Duplicate an existing node tree within the mesh |
| Upload File | Single .md, .json, .yaml, .csv, or .html file |
| Upload Folder (ZIP) | Directory structure or ZIP archive |
Import uses FileFormatParserRegistry to parse each file into a MeshNode based on its extension (StaticRepoImporter does the work for a repo-shaped import).
Programmatic Import
MeshOperations.Import is the inverse of MeshOperations.Export: it unpacks the archive, recreates every node with its path rewritten from the export root to targetNamespace (through IMeshService.CreateNode, carrying the caller's AccessContext), then re-uploads every content-collection file through the standard upload path.
var ops = new MeshOperations(hub); // a facade over the hub, not a DI service
ops.Import("org/acme/project", zipBytes)
.Subscribe(summary => logger.LogInformation("{Summary}", summary),
ex => logger.LogWarning(ex, "Import failed"));
// summary is JSON: {status,exportRoot,targetNamespace,nodesImported,filesImported}
⚠️
ImportNodesRequest/ImportNodesResponseare declared inMeshWeaver.Mesh.Contractand registered in the type registry, but no hub handlesImportNodesRequest— posting one gets no answer. UseMeshOperations.Importuntil a handler exists.
Copy
Copy duplicates a node and all its descendants to a new namespace. The source node's ID is preserved under the target.
Example: Copying org/acme to org/backup creates:
org/backup/acme(root)org/backup/acme/child1org/backup/acme/child2- … and so on for the full subtree
Options
- Force — overwrite existing nodes at the destination (default: skip existing)
Programmatic Copy
CopyNodeTree returns IObservable<int> and is cold — the copy runs on Subscribe, and the single emission is the count of upserted nodes. Subscribe, don't await:
NodeCopyHelper.CopyNodeTree(
meshService, meshService, hub,
sourcePath: "org/acme",
targetNamespace: "org/backup",
force: false)
.Subscribe(
nodesCopied => logger.LogInformation("Copied {Count} nodes", nodesCopied),
ex => logger.LogWarning(ex, "Copy failed"));
hub may be any hub — it supplies the caller identity and reply address; every per-node request is routed to hub.NodeOperationTarget(). Permission checks live in the CreateOrUpdateNodeRequest handler, and force: true updates an existing target rather than deleting it.
Move
Move relocates a node and its entire subtree to a new path. It requires Delete permission on the source and Create permission on the target.
The move is implemented at the persistence layer, handling both same-partition and cross-partition moves (including PostgreSQL). Descendants are moved first, then the root node is relocated and the source is deleted.
Programmatic Move
hub.Observe(new MoveNodeRequest("org/acme/old-name", "org/acme/new-name"),
o => o.WithTarget(address))
.Subscribe(
response =>
{
if (response.Message.Success)
Console.WriteLine($"Moved to: {response.Message.Node.Path}");
},
ex => logger.LogWarning(ex, "Move failed"));
File Format Details
Each content type serialises to a distinct, human-readable format. The sections below show the canonical shape for each.
Markdown (.md)
---
Name: "Getting Started"
Category: "Documentation"
Authors:
- "Jane Doe"
Tags:
- "tutorial"
---
# Getting Started
Your markdown content here...
NodeType defaults to Markdown and may be omitted. Only non-default values appear in the YAML front matter. If the name matches the ID and there's no other metadata, the YAML block may be omitted entirely.
Code (.cs)
// <meshweaver>
// Id: Person
// DisplayName: Person Data Model
// </meshweaver>
public record Person
{
[Key]
public string Id { get; init; } = string.Empty;
public string? Name { get; init; }
}
The <meshweaver> metadata block is optional. The primary type name is extracted from the code if no explicit Id is provided.
JSON (.json)
{
"id": "task-1",
"namespace": "org/acme/tasks",
"name": "Review submission",
"nodeType": "ACME/Task",
"content": {
"$type": "Task",
"title": "Review submission",
"priority": "High"
}
}
JSON is the fallback format for nodes that don't match markdown, agent, or code patterns.
MeshWeaver.Layout.Controls.Stack
.WithView(MeshWeaver.Layout.Controls.Markdown("### Node Operations at a Glance"))
.WithView(MeshWeaver.Layout.Controls.Markdown(
"| Operation | Requires | Scope |\n" +
"|-----------|----------|-------|\n" +
"| **Export** | `Permission.Export` (Editor+) | Node + full subtree → ZIP |\n" +
"| **Import** | Create permission on target | File, folder, or ZIP → nodes |\n" +
"| **Copy** | Read on source, Create on target | Node + full subtree → new namespace |\n" +
"| **Move** | Delete on source, Create on target | Node + full subtree → new path |"
))
See Also
- Node Menu Items — The menu system and the default item set
- Query Syntax — Search and filter nodes
- Node Type Configuration — Define custom node types
- CRUD Operations — Low-level data operations