Building a drag-and-drop page builder in Angular is easier than you might think thanks to GrapesJS. GrapesJS is an open-source web builder framework that lets you create HTML pages with a visual drag-and-drop editor. In this tutorial, we'll integrate GrapesJS into an Angular application to create a page builder with minimal code. You'll learn how to set up GrapesJS in Angular, use the official GrapesJS Basic Blocks plugin for ready-made content blocks, export the designed HTML/CSS, and even save it to a backend. We’ll be using Angular v20 (the latest stable version as of October 2025 and the latest GrapesJS library to ensure you’re up-to-date.
By the end of this step-by-step guide, you will have a working page builder in Angular where users can drag basic blocks (text, images, columns, etc.) onto a canvas, style them using GrapesJS’s built-in editors, and save the results. Let's get started!
Prerequisites
Make sure you have the following installed and set up before proceeding:
- Node.js and npm – Install the latest LTS version of Node.js which comes with npm.
- Angular CLI – Install globally with npm install -g @angular/cli. This tutorial uses Angular CLI 20.
- Basic Angular knowledge – Understanding of components, modules, and services in Angular.
- A code editor – e.g., VS Code, WebStorm, etc., to edit your project files.
- Internet connection – GrapesJS will be installed via npm.
Step 1: Setting Up the Angular Project
First, create a new Angular project using the Angular CLI. We’ll name the project grapesjs-pagebuilder (you can choose any name):
ng new grapesjs-pagebuilder
Follow the CLI prompts (you can skip adding routing for this simple demo, and use CSS as the stylesheet format). Once the project is generated, navigate into the project directory and start the development server:
cd grapesjs-pagebuilder ng serve -o
This will open the default Angular welcome page at http://localhost:4200/. Next, we'll set up the structure for our page builder. Generate a new Angular component that will host the GrapesJS editor. We’ll call it PageBuilderComponent:
ng generate component page-builder
This will create page-builder.component.ts, its HTML/CSS files, and update the app module. For this tutorial, we want our page builder component to take up the entire app. Open src/app/app.component.html and replace its content with the <app-page-builder></app-page-builder> selector (and remove the default template text). This ensures our page builder component is rendered as the main page.
Now we have a fresh Angular app ready for GrapesJS integration.
Step 2: Installing GrapesJS and the Basic Blocks Plugin
With our Angular project ready, let's install GrapesJS and the official Basic Blocks plugin. The Basic Blocks plugin provides a set of pre-built content blocks (text, images, columns, etc.) that we can drag and drop. Open a terminal in your project directory and run:
npm install grapesjs grapesjs-blocks-basic --save
This will add GrapesJS and the basic blocks plugin to your project dependencies. Once installed, we need to ensure Angular knows about the GrapesJS assets (CSS/JS files) and handle TypeScript definitions.
Include GrapesJS Assets in angular.json
GrapesJS comes with its own CSS and we also have a plugin JS to include. Open angular.json and locate the build options for your app (under projects -> YOUR_APP_NAME -> architect -> build -> options). Add the GrapesJS files to the styles and scripts arrays, for example:
"styles": [ "node_modules/grapesjs/dist/css/grapes.min.css", "src/styles.css" ], "scripts": [ "node_modules/grapesjs/dist/grapes.min.js", "node_modules/grapesjs-blocks-basic/dist/index.js" ]
This will ensure the GrapesJS core CSS and scripts (including the basic blocks plugin script) are loaded when the app runs. The CSS will style the GrapesJS editor (canvas, panels, etc.), and the JS scripts will make grapesjs and the plugin available globally.
Set Up TypeScript Definitions
If you try to import GrapesJS in an Angular TypeScript file, you might encounter errors because GrapesJS doesn’t ship with TypeScript type definitions. To avoid any TypeScript compilation errors (e.g., “Could not find a declaration file for module 'grapesjs'”), create a definition file. In the project src/ folder, add a new file named grapesjs.d.ts with the following content:
declare module 'grapesjs'; declare module 'grapesjs-blocks-basic';
This declares the modules so TypeScript will treat them as any and not complain when we import them. Now we’re ready to use GrapesJS in our Angular code.
(If you're using TypeScript and prefer stronger typing, GrapesJS provides a usePlugin helper for plugins to improve type safety. For simplicity, we'll proceed with the basic setup.)
Step 3: Integrating GrapesJS into an Angular Component
Now comes the main part: embedding the GrapesJS editor in our Angular component. We will initialize the GrapesJS editor in the PageBuilderComponent we created.
3.1 Component Template Setup
Open page-builder.component.html. We need a container element for the GrapesJS editor to mount. Add a div that will act as the editor canvas container. We also include a simple heading inside as initial content (GrapesJS can import the existing HTML inside the container). Additionally, we'll add a Save button that will trigger exporting the content:
<button (click)="savePage()">Save Page</button> <div #gjsContainer> <h2>My First GrapesJS Page</h2> </div>
Here:
- The #gjsContainer is a template reference that we'll use to get the DOM element in our component class.
- The <h2>My First GrapesJS Page</h2> inside the container is example content that GrapesJS will load initially (you can put any placeholder content here).
3.2 Component Class: Initializing GrapesJS
Open page-builder.component.ts. We will import GrapesJS and the plugin, then initialize the editor in the component’s lifecycle hooks.
First, import the necessary modules and declare a property for the editor instance:
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import grapesjs from 'grapesjs';
import basicBlocks from 'grapesjs-blocks-basic'; // Basic Blocks plugin
// ... (other imports like the service and Angular stuff)
@Component({
selector: 'app-page-builder',
templateUrl: './page-builder.component.html',
styleUrls: ['./page-builder.component.css']
})
export class PageBuilderComponent implements AfterViewInit, OnDestroy {
@ViewChild('gjsContainer', { static: false }) editorContainer!: ElementRef<HTMLDivElement>;
editor: any; // we'll store the GrapesJS editor instance here
constructor(private pageService: PageService) { }
Explanation:
- We use @ViewChild('gjsContainer') to capture the reference to the <div #gjsContainer> in our template
- . This gives us access to the actual DOM element where we want GrapesJS to render.
- We implement AfterViewInit to initialize GrapesJS after the view is ready (so that editorContainer is populated) and OnDestroy to clean up the editor when the component is destroyed.
- We import grapesjs and the plugin (grapesjs-blocks-basic) at the top. The plugin import gives us a function we can pass to GrapesJS.
Now, within the class:
ngAfterViewInit(): void {
// Initialize GrapesJS editor
this.editor = grapesjs.init({
// Bind editor to the div element
container: this.editorContainer.nativeElement, // using the ViewChild element:contentReference[oaicite:9]{index=9}:contentReference[oaicite:10]{index=10}
fromElement: true, // import initial content from the container's innerHTML:contentReference[oaicite:11]{index=11}
height: '700px',
width: 'auto',
// Disable local storage for now (no autosave)
storageManager: false,
// Include the Basic Blocks plugin
plugins: [basicBlocks],
pluginsOpts: {
[basicBlocks]: {
// You can customize the plugin options here (e.g., which blocks to include)
}
}
});
// GrapesJS editor is now mounted and visible.
}
Let's break down the important configuration options we passed to grapesjs.init():
- container – The element or selector to mount the editor. Here we pass the native DOM element from our ViewChild (this.editorContainer.nativeElement)
- . This attaches GrapesJS to the <div #gjsContainer> in our template.
- fromElement – Tells GrapesJS to load the editor content from the HTML inside the container element
- . Because we put an <h2>My First GrapesJS Page</h2> in the div, the editor will start with that as a component on the canvas.
- height / width – Sets the dimensions of the editor. We use 700px height (you can adjust this or use '100vh') and 'auto' width to fill the container width.
- storageManager – GrapesJS has a storage manager (e.g., to save to localStorage). We set this to false to disable autosaving for now
- . This prevents GrapesJS from trying to save the state automatically (we'll handle save manually).
- plugins – An array of plugins to use. We include our imported basicBlocks plugin here
- . This plugin adds a set of basic content blocks (text, link, image, video, columns, etc.) to the GrapesJS Block Manager.
- pluginsOpts – Options for the plugins. We provided an empty options object for basicBlocks (using the plugin function as the key). By default, the basic blocks plugin will add all its blocks (columns, text, image, etc.) with default settings
- . (You can customize it— for example, to exclude a block like Map, you could specify blocks: ['column1','column2',...,'video'] leaving out 'map' in the options
- . In our case, we'll use all the default blocks.)
Below is a summary table of the key GrapesJS configuration options we used and what they do:
After calling grapesjs.init(...), the GrapesJS editor UI will appear inside our component. By default, GrapesJS loads with a set of panels and buttons. For example, you'll see a top bar with options (like toggling the blocks panel, undo/redo, etc.), a canvas in the middle (where the <h2> appears), and on the right a style/settings panel. On the left side, there will be a block panel containing the Basic blocks provided by the plugin (text, link, image, video, etc.). You can drag these blocks onto the canvas to build your page.
Tip: By default, the block panel in GrapesJS is hidden until you click the “Blocks” button (on the top-left toolbar). Click the Blocks icon to toggle the blocks panel and see the available drag-and-drop blocks. GrapesJS’s default UI includes this toggle button. If needed, you can configure GrapesJS to show the block panel by default (for example, by using a custom panel or the appendTo option in blockManager config), but for now using the default behavior is fine.
3.3 Cleaning Up: Destroying the Editor on Component Destroy
To prevent memory leaks or issues if our component unloads, it's good practice to destroy the GrapesJS editor instance when the component is destroyed. We implement ngOnDestroy for our component:
ngOnDestroy(): void {
if (this.editor) {
this.editor.destroy();
}
}
}
This calls the editor’s destroy() method to properly unload event listeners and free resources. GrapesJS provides editor.destroy() for this purpose (it triggers destroy events internally to clean up). Now our GrapesJS instance will terminate cleanly if the user navigates away from the component.
At this point, we have GrapesJS fully integrated into Angular. You can run ng serve and navigate to the app to verify: you should see the GrapesJS editor interface, with your sample heading in the canvas and a toolbar. Try dragging a block (e.g., a text or image block) from the Blocks panel into the canvas to ensure everything works.
Step 4: Exporting the HTML and CSS from GrapesJS
One of the powerful features of GrapesJS is that it allows you to export the user-created page as clean HTML and CSS. We’ve added a “Save Page” button in our template; now we’ll implement the logic to extract the HTML/CSS from GrapesJS when this button is clicked.
In our PageBuilderComponent class, we already have a savePage() method connected to the button. Let's implement it:
savePage(): void {
// Get the HTML and CSS from GrapesJS editor
const html = this.editor.getHtml();
const css = this.editor.getCss();
console.log('Exported HTML:', html);
console.log('Exported CSS:', css);
// Optionally, send the content to the backend
this.pageService.savePage(html, css).subscribe({
next: response => console.log('Page saved successfully!', response),
error: err => console.error('Error saving page:', err)
});
}
Here we use GrapesJS Editor’s API methods getHtml() and getCss() to retrieve the current HTML markup and CSS styles from the canvas. GrapesJS will return the HTML string of what’s inside the canvas (the designed page), and the CSS string for all the styles that have been applied (including classes, etc.). We log them to the console for now, and then call a service to actually save them.
At this stage, if you click the "Save Page" button in the running app, you should see the generated HTML and CSS printed in the browser console. For example, the HTML might show a <div> structure with your blocks and content, and CSS might include classes GrapesJS created for styling.
Step 5: Posting the Page Content to a Backend (Angular Service)
In a real-world app, you would likely want to save the user’s page design to a backend server (e.g., to store in a database or file system). We’ll simulate that by creating an Angular service to send the HTML/CSS to a backend API endpoint.
5.1 Creating the Service
Generate a new service (if you haven't already) called PageService:
ng generate service page
This creates page.service.ts. In this service, we will use Angular’s HttpClient to post the page content to an API. Make sure you import HttpClientModule in your AppModule (in app.module.ts) so that HttpClient is available.
Now, open page.service.ts and implement a method to save the page:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class PageService {
private apiUrl = '/api/pages'; // URL to backend endpoint (change as needed)
constructor(private http: HttpClient) { }
savePage(html: string, css: string): Observable<any> {
const payload = { html, css };
// POST the HTML/CSS to the backend (replace with your actual API endpoint)
return this.http.post(this.apiUrl, payload);
}
}
In this code:
- We define an apiUrl (e.g., '/api/pages') which should point to your backend endpoint that will handle saving the page data. In a real app, replace this with a real URL (for example, an API route on your server). For demonstration, we use a relative path.
- The savePage method accepts the HTML and CSS strings, packages them into a JSON payload, and uses HttpClient.post to send the data to the server.
- We return an Observable so the caller can subscribe and handle the response or errors.
Ensure that this service is provided in root (which it is via the @Injectable providedIn property). We injected this service into our PageBuilderComponent earlier via the constructor.
5.2 Using the Service in the Component
We already injected PageService into the component and called this.pageService.savePage(html, css).subscribe(...) in the savePage() method. This will send the request when the button is clicked. In a real implementation, you might show a success message or navigate, etc., after a successful save. For our purposes, we just log the result.
Now, when you run the application and click "Save Page", the app will send a POST request to the specified endpoint with the page content. You can verify this in your browser’s dev tools network tab. (If you don’t actually have a backend running at that URL, you will get an error – which is expected. In practice, you’d implement the backend to handle the request. For now, our focus is on the front-end integration.)
Conclusion
Congratulations! You have built a basic drag-and-drop page builder in Angular using GrapesJS. 🎉
In this guide, we:
- Set up an Angular project and component for the page builder (using Angular v20).
- Installed GrapesJS and the official grapesjs-blocks-basic plugin for ready-to-use blocks.
- Integrated GrapesJS into an Angular component with proper lifecycle management (initializing in ngAfterViewInit and destroying in ngOnDestroy).
- Configured GrapesJS with essential options and loaded the Basic Blocks plugin to provide draggable content blocks.
- Exported the page’s HTML and CSS using GrapesJS APIs and implemented an Angular service to send this data to a backend.
Using GrapesJS within Angular gives you a powerful way to offer end-users a visual page building experience while leveraging Angular’s ecosystem. You can expand this foundation by adding more GrapesJS plugins (for example, rich text editors, custom blocks, etc.), handling user authentication for saving pages, and even building a library of custom blocks specific to your application. GrapesJS is very extensible – you could create custom block plugins or integrate the style manager to allow users to tweak styles more freely.
Next steps: You might want to explore GrapesJS’s other features like the Style Manager and Trait Manager which were automatically enabled in our setup (the right sidebar in the editor lets you change styles of selected elements, etc.). You can also configure the Storage Manager to persist data (e.g., to localStorage or directly to your backend via GrapesJS’s remote storage option) instead of using a manual save button. For a more advanced setup, consider using GrapesJS presets (like grapesjs-preset-webpage) which bundle a collection of plugins for a full-featured page editor – though in this tutorial we stuck to the basics to show how things work under the hood.
We hope this tutorial was helpful. Happy coding, and enjoy building awesome page builders with Angular and GrapesJS! 🚀
