From migrate-drupal-canvas-source
Connecting a Canvas component project to Acquia Source, configuring OAuth API clients, uploading components, troubleshooting authentication errors (401, client auth failed, no components found), and managing .env configuration
npx claudepluginhub ajv009/drupal-devkitThis skill uses the workspace's default tool permissions.
This skill covers connecting a Canvas Storybook AI project to an Acquia Source
Builds, bundles, validates, and deploys React code components to Webflow Designer via CLI. Configures projects with webflow.json and shares to workspace libraries.
Deploys Canva Connect API integrations to Vercel, Fly.io, and Cloud Run. Manages OAuth secrets, configures platform files, and sets up server-side token exchange.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
This skill covers connecting a Canvas Storybook AI project to an Acquia Source instance for component uploads and content management. Use this when setting up a new project, debugging upload/auth failures, or onboarding a new site.
The system uses two separate APIs, each requiring different OAuth configuration:
| API | Base Path | Purpose | CLI Command |
|---|---|---|---|
| Canvas REST API | /canvas/api/v0/ | Upload/manage components | canvas:upload |
| JSON:API | /<prefix> (e.g., /api) | Content management (pages) | content |
Both APIs authenticate via OAuth 2.0 Client Credentials flow through the
/oauth/token endpoint on the CMS URL.
| Endpoint | Method | Purpose |
|---|---|---|
/canvas/api/v0/config/js_component | GET / POST | List / create components |
/canvas/api/v0/config/js_component/<name> | GET / PUT | Get / update a component |
/canvas/api/v0/config/asset_library/global | GET / PUT | Get / update global CSS |
| Endpoint | Method | Purpose |
|---|---|---|
/<prefix>/page | GET / POST | List / create pages |
/<prefix>/node--article | GET / POST | Articles |
/<prefix>/media--image | GET / POST | Media images |
Each Acquia Source site has two URLs:
https://<ID>.cms.acquia.site — Used for API access, admin
panel, and all .env configurationhttps://<name>.acquia.site — The live public-facing siteAlways use the CMS URL for CANVAS_SITE_URL in .env.
Navigate to the admin panel:
https://<ID>.cms.acquia.site/admin/config/services/api-clients
Create or edit an API client with these required settings:
| Field | Value | Notes |
|---|---|---|
| Label | Any name (e.g., "Canvas CLI") | Display name only |
| Machine name | e.g., default_client | Becomes CANVAS_CLIENT_ID |
| Secret | e.g., secret | Becomes CANVAS_CLIENT_SECRET |
Enable Client Credentials grant type. This is essential — without it, the CLI cannot authenticate.
Add all three scopes:
| Scope | Required For |
|---|---|
canvas:asset_library | Uploading global CSS |
canvas:js_component | Uploading components |
member | Authenticated API access |
Under the "Client Credentials settings" section of the API client form:
This is the user context under which API operations execute. The User field is an autocomplete — start typing the username and select from the dropdown.
This is the most commonly missed step. Without a user assigned, the Canvas REST API returns 401 even though the OAuth token itself is valid. See Troubleshooting below.
Check your JSON:API configuration at:
https://<ID>.cms.acquia.site/admin/config/services/jsonapi
Note the URL prefix. Acquia Source sites often use api instead of the
Drupal default jsonapi.
.env FileCopy .env.example to .env and fill in the values:
# Base URL — must be the CMS URL, not the public URL
CANVAS_SITE_URL=https://<ID>.cms.acquia.site
# JSON:API prefix — check admin/config/services/jsonapi
CANVAS_JSONAPI_PREFIX=api
# OAuth credentials from Step 1
CANVAS_CLIENT_ID=default_client
CANVAS_CLIENT_SECRET=secret
# Component source directory
CANVAS_COMPONENT_DIR=./src/components
# Debug logging
CANVAS_VERBOSE=false
| Variable | Required | Default | Description |
|---|---|---|---|
CANVAS_SITE_URL | Yes | — | CMS base URL |
CANVAS_JSONAPI_PREFIX | No | jsonapi | JSON:API URL prefix |
CANVAS_CLIENT_ID | Yes | — | OAuth client machine name |
CANVAS_CLIENT_SECRET | Yes | — | OAuth client secret |
CANVAS_COMPONENT_DIR | No | ./components | Path to component source directory |
CANVAS_VERBOSE | No | false | Enable verbose CLI logging |
CANVAS_SCOPE | No | canvas:js_component canvas:asset_library | Custom OAuth scopes |
CONTENT_NO_AUTH | No | false | Disable auth for content scripts |
CONTENT_OAUTH_SCOPE | No | — | Custom scope for content API |
# Validate component structure
npm run canvas:validate
# Validate a specific component
npm run canvas:validate -- -c heading
# Upload all components
npm run canvas:upload
# Upload a specific component
npm run canvas:upload -- -c heading
Successful upload output:
22 succeeded, 0 failed
Components and global CSS uploaded successfully
Update the siteUrl in vite.config.js to match your CMS URL:
drupalCanvas({
componentDir: './src/components',
siteUrl: 'https://<ID>.cms.acquia.site',
jsonapiPrefix: 'api',
}),
Verify each component in the Canvas Code Editor:
<CMS_URL>/canvas/code-editor/component/<component_name>
Check that props, source code, and preview render correctly. Components should also be available in the Canvas page builder in the admin panel under the component library.
Cause: CANVAS_COMPONENT_DIR is not set or points to the wrong directory.
Fix: Add to .env:
CANVAS_COMPONENT_DIR=./src/components
The default is ./components, but this project uses ./src/components.
Cause: Wrong CANVAS_CLIENT_ID or CANVAS_CLIENT_SECRET.
Fix: Check the API client's machine name (not the label) in the admin
panel at /admin/config/services/api-clients. The machine name is the value
you need for CANVAS_CLIENT_ID.
Common mistake: using the label (e.g., "Test Client") instead of the machine
name (e.g., default_client), or using cli when the actual machine name is
different.
This is the most common and confusing error. The OAuth token is valid, but the Canvas REST API still rejects it. This happens when the API client is missing one or more of three required settings.
Diagnosis: You can confirm the token itself works by testing against JSON:API:
# Get a token
TOKEN=$(curl -s -X POST https://<ID>.cms.acquia.site/oauth/token \
-d "grant_type=client_credentials&client_id=default_client&client_secret=secret" \
| python3 -c "import sys,json;print(json.loads(sys.stdin.read())['access_token'])")
# This works — JSON:API accepts the token
curl -s -H "Authorization: Bearer $TOKEN" \
https://<ID>.cms.acquia.site/api | head
# This fails — Canvas API rejects it
curl -s -H "Authorization: Bearer $TOKEN" \
https://<ID>.cms.acquia.site/canvas/api/v0/config/js_component
Fix — check all three requirements on the API client:
canvas:asset_library,
canvas:js_component, memberThe third item (assigning a user) is almost always the missing piece. Without it, the token has no user context and the Canvas API treats the request as anonymous.
Possible causes:
CANVAS_SITE_URL)status: false in component.yml — change to status: true<CMS_URL>/canvas/code-editor/component/<name> for errorsCause: CANVAS_SITE_URL is wrong or points to the public URL instead of
the CMS URL.
Fix: Use the CMS URL (https://<ID>.cms.acquia.site), not the public URL
(https://<name>.acquia.site).
If another Acquia Source site is already working with Canvas uploads (e.g., a teammate's site), you can inspect its API client configuration for reference:
https://<their-ID>.cms.acquia.site/admin/config/services/api-clients
Look at the API client's grant types, scopes, and Client Credentials settings to compare with your own configuration.
Use this checklist when setting up a new project:
<ID>.cms.acquia.site)canvas:asset_library, canvas:js_component, memberapi vs jsonapi).env with CANVAS_SITE_URL, CANVAS_CLIENT_ID,
CANVAS_CLIENT_SECRET, CANVAS_JSONAPI_PREFIX, CANVAS_COMPONENT_DIRsiteUrl in vite.config.jsnpm run canvas:validate to verify component detectionnpm run canvas:upload to push components<CMS_URL>/canvas/code-editor/component/<name>)