Skip to content

Goal: get one working component from your terminal into your site’s Drupal Canvas component library.

  • A local component project scaffolded from the Nebula template: Canvas Workbench (a Storybook-style local preview on a Vite dev server that auto-configures the JSON:API client), the Canvas CLI, an ESLint config that validates components against Canvas requirements, and AI-agent rules in .agents/skills/.
  • One minimal component, validated and built locally.
  • That component pushed to your site and visible in Canvas.
  • A Source CMS site you can administer: the Edit button (top right on any page of the site) opens the Canvas visual editor. (No site? A self-serve Cloud Platform free trial gets you building on Acquia the same day, though its Drupal 11 application is the Cloud Platform path, not a Source CMS site; for one of those, request a Source demo.)
  • Node.js 22 (the scaffold pins it in .nvmrc; the dev server requires 20.19+ or 22.12+). npm ships with Node.
  • git
  1. Scaffold the project from the Nebula template

    Section titled “Scaffold the project from the Nebula template”
    Terminal window
    npx @drupal-canvas/create@latest my-components \
    --template acquia-nebula \
    --agents none

    --agents none skips generating extra per-agent rule files (for Cursor, Copilot, and similar); the template’s own .agents/skills/ rules are included regardless, and you can generate agent-specific files later if you want them.

    The tool fetches the template, installs dependencies with npm, and initializes a git repository:

    ┌ Drupal Canvas Create
    ◇ Fetched template
    ● No additional agents selected. Skipping compatibility setup.
    ◇ Installed dependencies with npm
    ◇ Initialized Git repository on main with initial commit
    ◇ Get started ────────────────────────╮
    │ │
    │ Created project in ./my-components │
    │ │
    │ Next steps: │
    │ cd my-components │
    │ npm run dev │
    │ │
    ├──────────────────────────────────────╯
    └ Canvas project created successfully.

    The scaffolded tree (trimmed):

    my-components/
    ├── .agents/skills/ # AI rules: canvas-*, nebula-*
    ├── .claude/skills/ # symlinked for Claude
    ├── .env.example # Canvas CLI credentials
    ├── .github/workflows/ # lint-pr.yml, tests.yml
    ├── .nvmrc # 22
    ├── AGENTS.md
    ├── canvas.config.json # component/page dirs, CSS, sync
    ├── eslint.config.js # @drupal-canvas/eslint-config
    ├── examples/
    │ ├── components/ # 25 examples to copy from
    │ ├── pages/homepage.json
    │ └── regions/ # header.json, footer.json
    ├── package.json # "dev": "canvas-workbench"
    └── src/
    ├── global.css # Tailwind CSS entry point
    └── layout.jsx
  2. Terminal window
    cd my-components
    npm run dev
    > canvas-workbench
    VITE v7.3.2 ready in 336 ms
    ➜ Local: http://localhost:5173/

    Open http://localhost:5173/. Canvas Workbench scans src/components/ for components and renders live previews. It’s empty right now; your first component appears here in a moment. (The examples/components/ directory is reference material to copy from, not part of the scanned library.) Leave it running.

  3. In a second terminal, from the project root:

    Terminal window
    npx canvas scaffold --name my-hero
    ┌ Drupal Canvas scaffold
    ◇ Created component
    │ Created: my-hero
    │ Directory: src/components/my-hero
    │ Component metadata: src/components/my-hero/component.yml
    │ Source file: src/components/my-hero/index.jsx
    │ CSS file: src/components/my-hero/index.css
    └ Scaffold completed

    index.jsx is the component itself: ordinary React, a default export, props with default values, and a content slot the editor fills with other components.

    src/components/my-hero/index.jsx
    /**
    * MyHero example component
    */
    const MyHero = ({
    greeting = 'Hello world!',
    ctaDisplayText = 'Click me!',
    ctaLink = 'https://example.com',
    content,
    }) => {
    return (
    <div className="my-hero-component">
    <h2 className="greeting">{greeting}</h2>
    <div className="content">{content}</div>
    <button type="button" className="cta">
    <a href={ctaLink}>{ctaDisplayText}</a>
    </button>
    </div>
    );
    };
    export default MyHero;

    Open src/components/my-hero/component.yml and change the second line from machineName: hello-world to machineName: my-hero. The scaffold writes a placeholder machine name, and it must match the directory name.

  4. Terminal window
    npx canvas validate
    ┌ Drupal Canvas validate
    ◇ Validated components
    │ Valid: my-hero
    └ Validation completed

    The new component also appears in Workbench, rendered with the example values from component.yml.

  5. Create Canvas CLI credentials on your site

    Section titled “Create Canvas CLI credentials on your site”

    In your site’s admin UI, go to API > API clients and select Add API client to create an API client. Enter a name, select the Client Credentials checkbox, add canvas:asset_library and canvas:js_component as scopes, and set a User (not the anonymous user) to serve as the author of the client’s actions. Save to generate the credentials. (Same flow as the auth quickstart, with Canvas scopes.)

    Copy .env.example to .env and fill in the values. The Canvas CLI reads these exact variable names, a separate OAuth client from the DRUPAL_* credentials the Content API uses, since it pushes components rather than reading content:

    .env
    CANVAS_SITE_URL=https://your-site.example.com
    CANVAS_CLIENT_ID=your-client-id
    CANVAS_CLIENT_SECRET=your-client-secret
  6. Terminal window
    npx canvas push

    The CLI plans the change (Components: 1 create) and asks Push these changes to https://your-site.example.com?; confirm with Yes. It then builds my-hero together with the global Tailwind CSS assets and uploads everything:

    ◇ Pushed components
    │ Created: my-hero
    ◇ Prepared assets
    ◇ Pushed assets
    │ Global CSS (Tailwind CSS build)
    └ ✓ Push completed
  7. canvas scaffold wrote status: false into component.yml, and the flag survived the push: my-hero is on the site but disabled, and a disabled component cannot be placed on a page. Change status: false to status: true in src/components/my-hero/component.yml, then push again and confirm:

    Terminal window
    npx canvas push
  8. Confirm the component is in the site’s component library

    Section titled “Confirm the component is in the site’s component library”

    Open any page of your site (the homepage works), click Edit (top right) to open Canvas, then click the Code (</>) icon in the left toolbar. My Hero is listed among the code-based components; select it to open it in the code editor with its Preview and Component data panels. It is now available to drag onto pages from the component library.

The Nebula template gave you a complete authoring environment. Canvas Workbench previews components locally on a Vite dev server that auto-configures the JSON:API client for requests against your Drupal site (mirroring the in-browser code editor). ESLint enforces the rules a component must satisfy to run in Canvas, and .agents/skills/ carries rules that teach AI coding agents the same conventions. canvas push compiled your React source, then uploaded the component (scope canvas:js_component) and the built CSS assets (scope canvas:asset_library) to your site, where it joined the component library alongside components created in the browser editor. (To remove a pushed component later, delete its directory from src/components/ and run npx canvas push again: the plan lists it as a delete.)

Your site’s library was empty, which is the one case where a push cannot take anything away. Against a site that already has components and pages, push reconciles the two sides and can plan deletes: read push to a live site before the next one.

This page was verified against @drupal-canvas/create 1.5.0 and @drupal-canvas/cli 0.20.1. The current template ships Canvas Workbench (its Storybook-style successor) and agent skills in .agents/skills/; older descriptions of the Nebula scaffold naming Storybook and a ruler AI-rules folder are out of date.

You are using Node.js 18.20.4. Vite requires Node.js version 20.19+ or 22.12+. Please upgrade your Node.js version.

npm run dev was started with an unsupported Node version. The project pins Node 22 in .nvmrc; run nvm use (or install Node 22) and start again.

Authentication Error: Client authentication failed (from canvas push)

the client ID or secret in .env is wrong, or the API client isn’t set up for this flow. Re-copy both values into .env from the client at API > API clients, and confirm the client has the Client Credentials checkbox selected with both canvas:asset_library and canvas:js_component scopes. If it still fails after that, the secret was likely lost or regenerated: create a fresh client as in step 5 and put its new ID and secret in .env.

API endpoint not found: /oauth/token with “Canvas module is not enabled”

the CLI reached a server, but not a Canvas-enabled site. Check CANVAS_SITE_URL points at your Source CMS site (no trailing slash, no path); the usual cause is a URL for some other server entirely.

Line 2, Column 14: Directory name "my-hero" does not match machineName "hello-world". (drupal-canvas/component-dir-name)

(from canvas validate): the scaffold’s placeholder machine name was left in place. Edit component.yml so machineName matches the component’s directory name.

✗ Push incomplete

(from canvas push): the site already had components or pages that aren’t in your project’s src/components/. canvas push syncs the site to match your project exactly, including deleting anything it doesn’t find locally; if one of those items is placed on a page or referenced by other configuration, Drupal refuses to delete it and the push fails. The push is not rolled back: whatever the run already applied stays applied, so read its Created: / Updated: / Deleted: lines to see what landed. To avoid the conflict, pull the site’s existing components into your project before pushing (push to a live site).

canvas push prompts Enter the site URL instead of pushing

the .env file is missing or not in the project root. Copy .env.example to .env in the directory you run the command from.

Was this page helpful?