# Copy a W&B saved-view  template across projects

> Source: <https://gist.github.com/umakrishnaswamy/072c70758b0c42003795ca48a697feac>
> Published: 2026-05-25 22:37:52+00:00

| """Copy a W&B saved-view template from one project's workspace to another. | |
| Reads the source saved view via its URL (the `?nw=<id>` query string identifies | |
| the saved view) and reconstructs an identical Workspace in the destination | |
| project — preserving sections/panels, workspace settings (smoothing, x-axis, | |
| outliers, etc.), and runset settings (filters, grouping, ordering, pinned | |
| columns, per-run colors/disabled state). | |
| Setup: | |
| pip install wandb wandb-workspaces | |
| export WANDB_API_KEY=<your key> # or have it in ~/.netrc via `wandb login` | |
| Usage: | |
| python copy_savedview.py \ | |
| --source-url "https://wandb.ai/ENTITY/SRC_PROJECT?nw=abcd1234" \ | |
| --dest-entity ENTITY \ | |
| --dest-project DST_PROJECT \ | |
| --new-name "Copied template" | |
| Notes: | |
| - --source-url must be a saved-view URL (?nw=<id>). Personal/default | |
| workspace URLs (?nw=nwuser<username>) are not supported by the underlying | |
| wandb-workspaces SDK | |
| - The destination project is auto-bootstrapped with a placeholder run if it | |
| doesn't exist yet, because the GraphQL upsertView mutation requires the | |
| project to exist server-side. | |
| """ | |
| import argparse | |
| import copy | |
| import os | |
| import sys | |
| import wandb | |
| import wandb_workspaces.workspaces as ws | |
| def ensure_project_exists(entity: str, project: str): | |
| """Make sure the destination project exists server-side. | |
| `Workspace.save()` upserts the view via GraphQL but returns 404 if the | |
| target project hasn't actually been provisioned. `api.create_project` and | |
| `api.project()` aren't always sufficient — initializing a tiny run is the | |
| most reliable way to guarantee the project exists before we upsert a view | |
| into it. | |
| """ | |
| api = wandb.Api() | |
| try: | |
| api.project(name=project, entity=entity) | |
| print(f"Destination project {entity}/{project} already accessible.") | |
| return | |
| except Exception: | |
| pass | |
| print(f"Bootstrapping destination project {entity}/{project} with a placeholder run ...") | |
| run = wandb.init( | |
| entity=entity, | |
| project=project, | |
| name="_bootstrap_for_savedview_copy", | |
| reinit=True, | |
| tags=["_bootstrap"], | |
| ) | |
| wandb.log({"_bootstrap": 1}) | |
| run.finish() | |
| def copy_workspace( | |
| source_url: str, | |
| dest_entity: str, | |
| dest_project: str, | |
| new_name: str, | |
| ) -> ws.Workspace: | |
| """Load the source saved view and rebuild it under the destination project.""" | |
| print(f"Loading source saved view from: {source_url}") | |
| src = ws.Workspace.from_url(source_url) | |
| print( | |
| f"Loaded view '{src.name}' from {src.entity}/{src.project} with " | |
| f"{len(src.sections)} section(s), " | |
| f"{sum(len(s.panels) for s in src.sections)} panel(s)." | |
| ) | |
| print(f" filters: {src.runset_settings.filters!r}") | |
| print(f" groupby: {[g.name for g in src.runset_settings.groupby]}") | |
| print(f" order: {[(o.item.name, o.ascending) for o in src.runset_settings.order]}") | |
| print(f" pinned_columns: {src.runset_settings.pinned_columns}") | |
| # Deep-copy the structural pieces so mutating them on the new workspace | |
| # doesn't disturb the loaded source object | |
| sections = copy.deepcopy(src.sections) | |
| settings = copy.deepcopy(src.settings) | |
| runset_settings = copy.deepcopy(src.runset_settings) | |
| dst = ws.Workspace( | |
| entity=dest_entity, | |
| project=dest_project, | |
| name=new_name, | |
| sections=sections, | |
| settings=settings, | |
| runset_settings=runset_settings, | |
| ) | |
| dst.save() | |
| print(f"\nCopied saved view URL:\n {dst.url}") | |
| return dst | |
| def main(): | |
| p = argparse.ArgumentParser( | |
| description="Copy a W&B saved-view template between projects." | |
| ) | |
| p.add_argument( | |
| "--source-url", | |
| required=True, | |
| help='Saved-view URL, e.g. "https://wandb.ai/ENTITY/PROJECT?nw=abcd1234"', | |
| ) | |
| p.add_argument("--dest-entity", required=True, help="Destination W&B entity (user or team)") | |
| p.add_argument("--dest-project", required=True, help="Destination W&B project") | |
| p.add_argument("--new-name", required=True, help="Name to display for the copied saved view") | |
| args = p.parse_args() | |
| api_key = os.environ.get("WANDB_API_KEY") | |
| if api_key: | |
| wandb.login(key=api_key, relogin=True) | |
| else: | |
| if not wandb.login(): | |
| sys.exit( | |
| "ERROR: no W&B credentials found. Set WANDB_API_KEY or run " | |
| "`wandb login` first." | |
| ) | |
| ensure_project_exists(args.dest_entity, args.dest_project) | |
| copy_workspace( | |
| source_url=args.source_url, | |
| dest_entity=args.dest_entity, | |
| dest_project=args.dest_project, | |
| new_name=args.new_name, | |
| ) | |
| if __name__ == "__main__": | |
| main() |
