🟢 HubSpot Developer Practical Textbook — 2026 Edition Developer Edition
Chapter 3  ·  Marketing & CMS API

Marketing & CMS API
practical guide

The overall picture and implementation patterns of Forms API, Email API, Blog API, Pages API, and HubDB API. Systematically learn how to implement marketing automation, content management, and dynamic data utilization using APIs.

Marketing Hub API
CMS Hub API
Time required: Approximately 100 minutes
3-1 Overview of Marketing & CMS API

Organize API categories related to Marketing Hub and CMS Hub.

Forms API /marketing/v3/forms Marketing
Email(Single Send)API /marketing/v3/transactional/single-email/send Marketing
Marketing Email API /marketing/v3/emails Marketing
Blog Posts API /cms/v3/blogs/posts CMS
Site Pages / Landing Pages API /cms/v3/pages/site-pages  |  /landing-pages CMS
HubDB API /cms/v3/hubdb/tables CMS
URL Mappings API /cms/v3/url-mappings CMS
Scope note: The Marketing API and CMS API each require different scopes. When creating a Private App contentformstransactional-email etc., Please assign a scope according to the API you use.
3-2 Forms API — Create form and get submitted data

Programmatically create and manage HubSpot forms, capture and leverage submission data.

Forms API endpoint
GET
/marketing/v3/forms
Get form list
GET
/marketing/v3/forms/{formId}
Get specific form details
POST
/marketing/v3/forms
create new form
PATCH
/marketing/v3/forms/{formId}
Update form
POST
/submissions/v3/form-data/submit/{portalId}/{formGuid}
Send data to form (link from external form)
GET
/form-integrations/v1/submissions/forms/{formGuid}
Get form submission data
Node.js — form creation
import { Client } from '@hubspot/api-client'; const client = new Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN }); //Create contact form const form = await client.marketing.forms.formsApi.create({ formType: 'hubspot', name: 'Contact Form 2026', configuration: { language: 'ja', cloneable: false, postSubmitAction: { type: 'thank_you', value: 'Thank you for contacting us. ', }, }, fieldGroups: [ { fields: [ { fieldType: 'email', name: 'email', label: 'email address', required: true, }, { fieldType: 'text', name: 'firstname', label: 'Name', required: true, }, ], }, { fields: [ { fieldType: 'textarea', name: 'message', label: 'Inquiry details', required: true, }, ], }, ], }); console.log(`Form created: ${form.id}`);
Node.js — Send to HubSpot from external forms
// Send from your own form to HubSpot Forms (no-code integration pattern) async function submitToHubSpot(formGuid, data) { const portalId = process.env.HUBSPOT_PORTAL_ID; const url = `https://api.hsforms.com/submissions/v3/integration/submit/${portalId}/${formGuid}`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fields: [ { objectTypeId: '0-1', name: 'email', value: data.email }, { objectTypeId: '0-1', name: 'firstname', value: data.name }, { objectTypeId: '0-1', name: 'message', value: data.message }, ], context: { pageUri: data.pageUrl, pageName: data.pageName, ipAddress: data.ipAddress, }, }), }); if (!response.ok) throw new Error(`Submit failed: ${response.status}`); return await response.json(); }
Forms API and Submissions API: of the formstructure managementfor /marketing/v3/forms, to formdata transmission(Linking from external form) hsforms.com Use the Submissions endpoint of . Although the latter can be sent without a Private App Token, we recommend sending the IP address or pageUri to prevent spam.
3-3 Transactional Email API — Single email sending

Send transaction emails such as purchase confirmations and password resets from the API.

📧 Transactional Email Requirements

How to use the Transactional Email API Marketing Hub Professional or higher is required. You can also use HubSpot's email tools before sending.Create a transactional email templateAnd Specify that email ID and send. scope transactional-email is required.

Node.js — Single Send Email
// Send transactional email const result = await client.marketing.transactionalApi.sendEmail({ emailId: 12345678, // ID created with the HubSpot email tool message: { to: 'customer@example.com', cc: ['support@mycompany.com'], replyTo: 'noreply@mycompany.com', sendId: `order-confirm-${orderId}`, // Unique ID to prevent duplicate sending }, contactProperties: { firstname: 'Taro', email: 'customer@example.com', }, customProperties: { order_id: 'ORD-2026-001', order_amount: '¥49,800', product_name: 'Pro Plan (Annual)', login_url: 'https://app.example.com/login', }, }); console.log(`Status: ${result.sendResult}`); // "SENT" | "QUEUED" | "REJECTED"
Node.js — Prevent duplicate sending with sendId
// Requests with the same sendId will be treated as duplicates and will not be resent // Safe to submit multiple times for the same order const sendId = `order-${orderId}-confirm`; try { await client.marketing.transactionalApi.sendEmail({ emailId: CONFIRM_EMAIL_ID, message: { to: customerEmail, sendId }, customProperties: { order_id: orderId }, }); } catch (err) { // 409: Already sent (duplicate sendId) → Treat as normal if (err.response?.status !== 409) throw err; console.log('Email already sent (duplicate sendId). Skipping.'); }
⚠ Difference between Marketing Email and Transactional Email: Marketing emails (broadcast) will not be sent to contacts who have opted out. Transactional EmailEmail necessary for commercial transactions(Purchase confirmation, password reset, etc.) only. It will also be sent to contacts who have opted out. Please strictly follow the intended use.
3-4 Blog Posts API — Blog article management

Create programs, update, and publish schedules for blog articles.

Blog Posts API endpoint
GET
/cms/v3/blogs/posts
Blog article list (filter/sort supported)
GET
/cms/v3/blogs/posts/{objectId}
Get specific article
POST
/cms/v3/blogs/posts
Create a new blog post (draft)
PATCH
/cms/v3/blogs/posts/{objectId}
Update article
POST
/cms/v3/blogs/posts/{objectId}/draft/push-live
Publish your draft
POST
/cms/v3/blogs/posts/schedule
Schedule a publishing date and time
Node.js — Create and publish blog posts
// Step 1: Create a draft const post = await client.cms.blogs.blogPostsApi.create({ name: 'HubSpot API Development Best Practices 2026', contentGroupId: BLOG_ID, // Blog ID (check in your HubSpot admin screen) slug: 'hubspot-api-best-practices-2026', metaDescription: 'Explaining the latest best practices for developing with the HubSpot API. ', postBody: ` <h2>Introduction</h2> <p>With HubSpot API v3...</p> `, tagIds: [TAG_ID_1, TAG_ID_2], featuredImage: 'https://example.com/og-image.png', featuredImageAltText: 'HubSpot API development', publishDate: '2026-04-01T09:00:00.000Z', currentState: 'DRAFT', }); // Step 2: If you want to publish immediately await client.cms.blogs.blogPostsApi.pushLiveDraft(post.id); // Step 2 (different pattern): Schedule publication by specifying date and time await fetch( `https://api.hubapi.com/cms/v3/blogs/posts/schedule`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ id: post.id, publishDate: '2026-04-01T09:00:00.000Z', }), } );
Node.js — List and filter blog articles
// Get only published articles (last 30) const posts = await client.cms.blogs.blogPostsApi.getPage( undefined, // after (pagination) 30, // limit undefined, undefined, undefined, 'PUBLISHED', // state filter undefined, undefined, undefined, undefined, 'publishDate', // sort 'DESCENDING' ); console.log(posts.results.map(p => ({ id: p.id, name: p.name, url: p.url })));
3-5 Pages API — Site page/landing page management

Create, clone, and publish CMS Hub pages programmatically.

📄 Site Pages

Regular website page.
Endpoint:/cms/v3/pages/site-pages
Usage: Company profile, product page, FAQ, etc.

🎯 Landing Pages

Marketing landing page.
Endpoint:/cms/v3/pages/landing-pages
Purpose: Campaign, white paper DL, etc.

Node.js — Creating and publishing landing pages
// Create a landing page (draft state) const page = await fetch( 'https://api.hubapi.com/cms/v3/pages/landing-pages', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Spring Campaign 2026 LP', slug: 'spring-campaign-2026', templatePath: '@hubspot/growth/templates/landing-page.html', headHtml: '<meta name="description" content="Spring Campaign Special">', metaDescription: 'Spring campaign special page', currentState: 'DRAFT', }), } ).then(r => r.json()); // Publish draft immediately await fetch( `https://api.hubapi.com/cms/v3/pages/landing-pages/${page.id}/draft/push-live`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}` }, } );
Node.js — Page duplication (mass production from template)
// Pattern for mass producing new campaign pages by duplicating existing pages async function clonePage(sourcePageId, newName, newSlug) { const res = await fetch( `https://api.hubapi.com/cms/v3/pages/landing-pages/${sourcePageId}/clone`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newName }), } ); const cloned = await res.json(); // update slug await fetch( `https://api.hubapi.com/cms/v3/pages/landing-pages/${cloned.id}`, { method: 'PATCH', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ slug: newSlug }), } ); return cloned; }
3-6 HubDB API — Dynamic table data management

HubDB is a database that can be used like a spreadsheet. Used when embedding dynamic content in CMS pages.

🗄 HubDB use cases

HubDB is used when you want to display dynamic data on a CMS page. for exampleStore list page, team member introduction, price list, FAQetc., Store structured data in HubDB tables and call and display it from HubL templates. It can also be read and written from the API, so it can also be used to synchronize with external systems.

HubDB API endpoint
GET
/cms/v3/hubdb/tables
Get table list
POST
/cms/v3/hubdb/tables
create new table
GET
/cms/v3/hubdb/tables/{tableIdOrName}/rows
Get table row data
POST
/cms/v3/hubdb/tables/{tableIdOrName}/rows
add row
PATCH
/cms/v3/hubdb/tables/{tableIdOrName}/rows/{rowId}/draft
Update row (draft)
POST
/cms/v3/hubdb/tables/{tableIdOrName}/draft/publish
Publish draft (entire table)
Node.js — HubDB table creation
// Create store information table const table = await client.cms.hubdb.tablesApi.createTable({ name: 'store_locations', label: 'List of stores', useForPages: true, // Use as page template columns: [ { name: 'store_name', label: 'Store name', type: 'TEXT' }, { name: 'address', label: 'address', type: 'TEXT' }, { name: 'phone', label: 'telephone number', type: 'TEXT' }, { name: 'prefecture', label: 'prefectures', type: 'SELECT', options: [ { name: 'Tokyo', type: 'option', order: 1 }, { name: 'Osaka Prefecture', type: 'option', order: 2 }, { name: 'Aichi prefecture', type: 'option', order: 3 }, ], }, { name: 'latitude', label: 'latitude', type: 'NUMBER' }, { name: 'longitude', label: 'longitude', type: 'NUMBER' }, { name: 'is_open', label: 'Open', type: 'BOOL' }, ], });
Node.js — Adding and publishing rows
// add row data await client.cms.hubdb.rowsApi.createTableRow(table.id, { values: { store_name: 'Shibuya store', address: '1-1-1 Shibuya, Shibuya-ku, Tokyo', phone: '03-1234-5678', prefecture: { name: 'Tokyo' }, latitude: 35.6580, longitude: 139.7016, is_open: true, }, }); // Publish changes (draft → live) await client.cms.hubdb.tablesApi.publishDraftTable(table.id);
Node.js — HubDB data retrieval and filtering
// Get open stores by filtering by prefecture const rows = await client.cms.hubdb.rowsApi.getTableRows( 'store_locations', // table name undefined, // sort undefined, // after 100, // limit undefined, undefined, 'is_open=true', // filter ); console.log(rows.results.map(r => r.values)); // or REST API direct call (more flexible filtering) const response = await fetch( 'https://api.hubapi.com/cms/v3/hubdb/tables/store_locations/rows?prefecture__contains=Tokyo&is_open=true&limit=50', { headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}` } } ); const data = await response.json();
HubDB draft management: HubDB isLive version and draft versionIt has two states. After adding or updating a row, be sure to publishDraftTable() Please run and publish. If you do not publish it, it will not be reflected on the CMS page.
3-7 URL Mappings API — Redirect management

Programmatically manage redirects from old URLs to new URLs. Especially useful when renewing a site.

Node.js — Bulk redirect registration
// Register a large number of redirects at once from CSV or array const redirects = [ { routePrefix: '/old-blog/article-1', destination: '/blog/new-article-1', redirectStyle: 301 }, { routePrefix: '/old-about', destination: '/about-us', redirectStyle: 301 }, { routePrefix: '/campaign-2025', destination: '/campaign-2026', redirectStyle: 302 }, ]; for (const redirect of redirects) { await fetch('https://api.hubapi.com/cms/v3/url-mappings', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.HUBSPOT_ACCESS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ routePrefix: redirect.routePrefix, destination: redirect.destination, redirectStyle: redirect.redirectStyle, isOnlyAfterNotFound: false, isMatchQueryString: false, }), }); // Rate limiting measures await new Promise(r => setTimeout(r, 100)); } console.log(`${redirects.length} redirects registered.`);
3-8 Practical pattern: Syncing external systems with HubSpot

Learn practical patterns for periodically syncing external data to HubDB and Blog API.

🔄 Examples of sync patterns

Information from internal product databases (MySQL, etc.)Sync daily to HubDBAnd Always see up-to-date product information on HubSpot's CMS pages. Synchronization can be implemented using Node.js scripts + scheduled execution of GitHub Actions.

Node.js — Delta sync script to HubDB
/** * Delta-sync external data to HubDB * 1. Get external data * 2. Compare with current data of HubDB * 3. Add/update/delete * 4. Publish and publish */ async function syncToHubDB(tableId, externalData) { // Get all existing rows const existing = await client.cms.hubdb.rowsApi.getTableRows(tableId, undefined, undefined, 1000); const existingMap = new Map(existing.results.map(r => [r.values.external_id, r])); let added = 0, updated = 0, deleted = 0; // Add/Update for (const item of externalData) { const existing_row = existingMap.get(item.id); if (!existing_row) { await client.cms.hubdb.rowsApi.createTableRow(tableId, { values: { external_id: item.id, name: item.name, price: item.price }, }); added++; } else if (existing_row.values.price !== item.price) { await client.cms.hubdb.rowsApi.updateDraftTableRow(tableId, existing_row.id, { values: { price: item.price }, }); updated++; } existingMap.delete(item.id); } // Delete (rows that no longer exist in the external data) for (const [, row] of existingMap) { await client.cms.hubdb.rowsApi.purgeDraftTableRow(tableId, row.id); deleted++; } // public await client.cms.hubdb.tablesApi.publishDraftTable(tableId); console.log(`Sync done: +${added} / ~${updated} / -${deleted}`); }
.github/workflows/hubdb-sync.yml
name: Sync Products to HubDB on: schedule: - cron: '0 2 * * *' # Run every day at 02:00 UTC (11:00 JST) workflow_dispatch: # Can also be executed manually jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm ci - name: Run sync env: HUBSPOT_ACCESS_TOKEN: ${{ secrets.HUBSPOT_ACCESS_TOKEN }} DB_CONNECTION_STRING: ${{ secrets.DB_CONNECTION_STRING }} run: node scripts/sync-products.js
3-9 Summary of this chapter

Review before proceeding to the next chapter (Custom Object & Schema Design).

✅ Chapter 3 Checklist

  • Understand the categorization and scoping requirements for Marketing API and CMS API
  • Create forms programmatically with the Forms API
  • You can submit data from external forms to HubSpot Forms (Submissions API)
  • Transactional Email API lets you send transactional emails
  • Can implement duplicate sending prevention using sendId
  • Blog Posts API allows you to create, update, publish, and schedule articles.
  • You can create, duplicate, and publish landing pages using the Pages API.
  • Understood the table structure of HubDB (live version/draft version)
  • You can create, add rows, and publish HubDB tables.
  • Understand the script to incrementally sync external data to HubDB
  • You can register redirects in bulk using the URL Mappings API.
  • You can automate periodic synchronization by scheduling GitHub Actions.
About the next chapter (Chapter 4): Learn custom object and schema design. Business data structures that cannot be expressed using standard objects We will design it as a custom object and explain property types, associations, and Enterprise-specific functions.