# Export HTML and CSS from GrapesJS: one standalone file per page

*Source: https://www.editorstack.cc/guides/grapesjs-export-html-css — last updated 2026-09-17. Licensed CC BY 4.0 with attribution to EditorStack.*

*Tested with GrapesJS 0.23.5. The code on this page runs in CI from examples/guides/grapesjs-export-html-css in the site repository.*

## Summary

- For each page, pass page.getMainComponent() to editor.getHtml() and editor.getCss(); both are then scoped to that page, so one page's CSS does not leak into another's file.
- getHtml() on a page's main component returns a complete <body> element, so a document template wraps it in html and head but must not add a second body.
- getCss() drops rules no component on the page uses unless you pass keepUnusedStyles: true; our test confirms both behaviours and that the exported file renders the same computed styles as the canvas.

## What GrapesJS gives you

Two calls, both documented on the Pages module:

- `editor.getHtml({ component })` returns the markup of a component and its children.
- `editor.getCss({ component })` returns the CSS rules that apply to them.

With a page's main component as `component`, you get that page and nothing else, which is what
an export of a multi-page project needs. The
[GrapesJS playground](/playground) has a code view if you want to see the raw output first.

## The exporter

```ts title="export-pages.ts"
import type { Editor } from 'grapesjs';

export type ExportedPage = {
  id: string;
  name: string;
  filename: string;
  /** A complete, standalone HTML document. */
  document: string;
};

const escapeHtml = (value: string) =>
  value.replace(/[&<>"']/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[ch] ?? ch);

const slugify = (value: string) =>
  value
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '') || 'page';

/** One standalone HTML file per page in the project. */
export function exportPages(editor: Editor): ExportedPage[] {
  return editor.Pages.getAll().map((page, index) => {
    const component = page.getMainComponent();
    // getHtml on a page's wrapper returns its <body>…</body>, component scripts included.
    const body = editor.getHtml({ component });
    // Only the rules this page's components use; pass keepUnusedStyles: true to keep all.
    const css = editor.getCss({ component }) ?? '';
    const name = page.getName() || `Page ${index + 1}`;

    const document = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${escapeHtml(name)}</title>
<style>${css}</style>
</head>
${body}
</html>`;

    return { id: page.getId(), name, filename: `${slugify(name)}.html`, document };
  });
}

/** Browser download without a dependency. */
export function downloadFile(filename: string, contents: string, type = 'text/html') {
  const url = URL.createObjectURL(new Blob([contents], { type }));
  const link = Object.assign(document.createElement('a'), { href: url, download: filename });
  link.click();
  setTimeout(() => URL.revokeObjectURL(url), 0);
}
```

Three things we confirmed by running it rather than assuming:

- **`getHtml` returns the body.** On a page's main component, the output starts with `<body`,
  so the template adds `html` and `head` around it and nothing else. The test asserts each
  document has exactly one body element.
- **CSS is page-scoped.** The Home page's CSS contains `.title` and not the About page's
  `.about-title` rule.
- **Unused rules are dropped.** A `.unused-rule` defined on the Home page but matching no element
  is absent from `getCss({ component })` and present with `keepUnusedStyles: true`. If an
  exported page is missing a rule it needs, that option keeps every rule.

## A project to export

```ts title="main.ts"
import grapesjs from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
import { downloadFile, exportPages } from './export-pages';

const editor = grapesjs.init({
  container: '#gjs',
  height: 'calc(100vh - 40px)',
  storageManager: false,
  projectData: {
    pages: [
      {
        id: 'home',
        name: 'Home',
        component: `<main class="home"><h1 class="title">Welcome</h1><p>Home page copy.</p></main>
          <style>.title { color: rgb(200, 30, 30); } .unused-rule { color: blue; }</style>`,
      },
      {
        id: 'about',
        name: 'About us',
        component: `<main><h1 class="about-title">About</h1></main>
          <style>.about-title { font-size: 48px; }</style>`,
      },
    ],
  },
});

document.getElementById('export')?.addEventListener('click', () => {
  for (const page of exportPages(editor)) downloadFile(page.filename, page.document);
});

// Exposed for the test harness.
Object.assign(window, { editor, exportPages });
```

The download helper creates one file per page. For a real product you would more likely send
`document` to your server, write it to object storage or a CDN, or save it next to the project
JSON as described in [saving to a database](/guides/grapesjs-save-load-database).

## What our test checks

The example is bundled with esbuild and run in headless Chromium. The test asserts that:

1. `getHtml` on the Home page's main component starts with `<body`;
2. the Home page's CSS includes its own rule, excludes the About page's rule and excludes the
   unused rule, and `keepUnusedStyles: true` brings the unused rule back;
3. two documents are produced, the second named `about-us.html`, each with one `<body>`;
4. clicking Export triggers two browser downloads with those filenames;
5. the Home document, loaded on its own page, gives `h1.title` the same computed colour,
   `rgb(200, 30, 30)`, as the heading inside the editor canvas.

> **Sponsored — GJS.Market:** Export and code plugins for GrapesJS — GJS.Market's plugin directory includes export, code-view and publishing plugins for GrapesJS. GJS.Market funds this site; check each plugin's GrapesJS version support.

## What this guide does not cover

- **Assets.** Image URLs are exported as they are. If they point at your storage, the exported
  file depends on it; copying assets alongside is not covered. Uploading them is in
  [the S3 guide](/guides/grapesjs-asset-manager-s3).
- **Tailwind and other utility CSS.** Classes are exported, their CSS is not generated by GrapesJS.
  [GrapesJS with Tailwind](/guides/grapesjs-tailwind) compiles it from the exported HTML.
- **Email HTML.** Email clients need table layouts and inline styles; that is a different export,
  covered in [the email builder guide](/guides/grapesjs-email-builder).
- **Sanitising for publication.** If untrusted users build pages that other people view, treat the
  exported HTML as untrusted input on the serving side.
- **Hosted alternatives.** If portable HTML output is the reason you are looking at GrapesJS, the
  [GrapesJS review](/libraries/grapesjs) explains why that is its main strength, and
  [GrapesJS vs Webflow](/compare/grapesjs-vs-webflow) compares it with a hosted builder.


## Frequently asked questions

### Should I store the exported HTML instead of the project JSON?

No. Store the project JSON to reopen a project in the editor; the GrapesJS documentation warns that loading from HTML loses information. Store exported HTML and CSS as well if a public page needs them, which our save-and-load guide does in the same request.

### Does the export include JavaScript?

Component scripts, if you use them, are included in getHtml() output because the jsInHtml option defaults to true. The example components have no scripts, so we did not test that path.

### Is there a plugin that exports a ZIP?

grapesjs-plugin-export (version 1.0.12, BSD-3-Clause, last published June 2023) adds a ZIP export command. We did not test it with 0.23.5. The code in this guide has no dependency beyond GrapesJS.


## Sources

1. [GrapesJS documentation: Pages (per-page getHtml and getCss)](https://grapesjs.com/docs/modules/Pages.html) — accessed 2026-09-17
2. [GrapesJS API reference: Editor (getHtml, getCss)](https://grapesjs.com/docs/api/editor.html) — accessed 2026-09-17
3. [GrapesJS documentation: Storage Manager (project data vs HTML)](https://grapesjs.com/docs/modules/Storage.html) — accessed 2026-09-17
4. [npm registry metadata for grapesjs-plugin-export](https://registry.npmjs.org/grapesjs-plugin-export) — accessed 2026-09-17
