Working with a Monorepo

Canvas manages plugin code as one git repository per plugin. Canvas Platform hosts that repository, canvas init creates its local copy with origin pointing at Canvas Platform, and canvas deploy pushes your commits to it, so the repository you work in and the plugin’s repository on Canvas Platform share one history.

That works well for a team that develops each plugin on its own. Other teams keep many plugins in one repository, often a monorepo beside shared code and services, and review and release them through that repository. A plain canvas deploy refuses a package inside such a repository, because it would push the whole repository as the plugin and point the repository’s origin at Canvas Platform.

canvas deploy --snapshot is for that case. It publishes each plugin from where it sits, and your repository stays the source of truth. Canvas Platform still keeps one repository per plugin: each snapshot of a changed package adds one commit to that plugin’s main holding the package folder as committed in your repository, and a snapshot of an unchanged package pushes nothing.

Lay out each plugin #

Each plugin’s package folder, the folder holding CANVAS_MANIFEST.json, can sit anywhere in your repository. Name it after the plugin’s publisher-prefixed name, <org prefix>__<package>, and use that name for the manifest name and the package’s imports:

your-repo/
├── plugins/
│   ├── acme__intake/              # package folder: this is what a snapshot ships
│   │   ├── CANVAS_MANIFEST.json
│   │   └── handlers/
│   └── referrals/
│       ├── acme__referrals/       # package folder
│       └── tests/                 # stays in your repository
└── services/                      # stays in your repository

A snapshot ships only the package folder. Tests, other plugins and the rest of your repository never reach Canvas Platform. The manifest name decides which Canvas Platform repository a snapshot goes to, so rename plugins in one reviewed change before the first deploy, and every checkout and pipeline then deploys to the same place.

Deploy from your machine #

Sign in once with canvas login, commit your changes, and pass the path to the package folder:

$ canvas deploy plugins/acme__intake --snapshot --instance acme-staging

deploy builds one commit on top of the plugin’s main on Canvas Platform, pushes it from a temporary repository, and deploys it. Your repository’s origin, git config and index are never changed. Uncommitted changes in the package are refused rather than committed. Add --push-only to publish the code and its catalog listing without deploying to an instance.

Deploy from CI #

A pipeline signs in as a service account: create one on the Credentials page in Canvas Platform, store its token as a CI secret, and expose it as CANVAS_PLATFORM_TOKEN. A snapshot works from a shallow checkout and needs no git identity on the runner. This GitHub Actions workflow deploys one plugin whenever a change to it merges:

on:
  push:
    branches: [main]
    paths: ["plugins/acme__intake/**"]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install canvas
      - run: canvas deploy plugins/acme__intake --snapshot --instance acme-staging
        env:
          CANVAS_PLATFORM_TOKEN: ${{ secrets.CANVAS_PLATFORM_TOKEN }}

The commit on Canvas Platform is authored as the service account, and its message names the commit and path it came from (Monorepo-Commit and Monorepo-Path). Give each plugin its own job, or loop over the plugins a merge changed.

With Canvas Studio and gated release #

When your organization builds plugins in Canvas Studio with gated release pointed at your repository, Studio’s work reaches your instances only through your repository’s review:

Gated release from Canvas Studio through a monorepo1. Studio users build and test a plugin. 2. Publishing opens a pull request that writes the plugin into its folder in your repository. 3. Your engineers review it, edit it if needed, and merge it. 4. CI or an engineer runs canvas deploy with --snapshot, which adds one commit to the plugin's main on Canvas Platform and deploys it. 5. Studio brings the snapshot on main into the project, and the next iteration starts from it.Canvas StudioYour repositoryCanvas PlatformPublish--snapshotpushed by CI or an engineerreviewed codenext iteration1 · Build and testwork in progress stays on adevelopment branch and theStudio test instance2 · Pull requestStudio writes the plugininto its folder in your repo(GitLab: merge request)3 · Review and mergeapprove, push edits, orchange it after merging,then merge to main4 · Snapshot on mainone commit holding thepackage as merged, deployedto your instances5 · Studio picks it upbrings the snapshot onmain into the project, andthe next iteration starts there
  1. Build and test. Studio users build the plugin and test it on the Studio test instance. Studio keeps this work in progress on a development branch of the plugin’s Canvas Platform repository, so the plugin’s main holds only code that has been through your review.
  2. Pull request. Publishing from Studio’s Final Review opens a pull request in your repository (on GitLab, a merge request) that writes the plugin into its folder there.
  3. Review and merge. Your engineers review it like any other change. They can approve it as it is, push edits to its branch, or change the plugin after merging.
  4. Snapshot. Once it merges, CI or an engineer runs canvas deploy <package folder> --snapshot. The package folder is the one holding CANVAS_MANIFEST.json inside the folder Studio’s pull request writes. Canvas Platform’s main gets one commit with the plugin exactly as merged, and it deploys to the instances you name.
  5. Studio picks it up. Studio brings the snapshot on Canvas Platform’s main into the project, so your engineers’ edits become the starting point of the next iteration instead of being overwritten by it.

When a snapshot stops #

deploy --snapshot exits non-zero without pushing when:

  • The package has uncommitted changes, or holds a git submodule, a symbolic link or a Git LFS file. Commit the changes, or replace those entries with regular files.
  • HEAD is behind the last snapshot. Update the checkout, for example with git pull, and deploy again.
  • main on Canvas Platform has changes the snapshot would replace, such as an edit to the plugin’s catalog listing made in Canvas Platform. deploy lists the files. To keep the changes, canvas clone the plugin, copy its versions of those files into your repository, commit them, and deploy again. To replace them, run the deploy at a terminal and confirm.

The canvas deploy reference covers each case in full.