All docsConnect your site

Connect

Connect your site

A publishing connection is where approved articles go live. You set it up once, in Settings under Connections, or during setup. pangoling never publishes anything you have not approved, unless you turn on auto-publish yourself.

Most connections have a Test connection button. Use it before you save. Shopify and Git / Markdown have none, so for those the first publish is what tells you a credential is wrong. Either way, run the launch check next, so you know articles render properly on the real site.

Credentials are stored encrypted, and each one is only ever sent to the host it was saved for.

Pick your platform

Your site runs onUsePublishes as
WordPress (self-hosted or WordPress.com)WordPressa post or a page
GhostGhosta post
WebflowWebflowa live item in your blog collection
HubSpot CMSHubSpota blog post, published or scheduled
Notion, or a Notion-powered site (Super, Potion)Notiona page in a database
ShopifyShopifya page
A static site built from a GitHub repo (Next.js, Astro, Hugo, Eleventy)Git / Markdowna Markdown or MDX file, committed
Anything else with a developer behind itCustom site (webhook)whatever your endpoint does with it

If you do not want pangoling to publish at all, choose No publishing. Drafts stay in pangoling for you to copy out.

WordPress

  1. In WordPress admin, open Users, then Profile (your own user).
  2. Scroll to Application Passwords, type a name like "pangoling", and click Add New.
  3. Copy the generated password, spaces included.

Then fill in:

  • Site URL: your WordPress home URL, with no trailing slash.
  • Username: the login the application password belongs to.
  • Post type: posts for blog posts, pages for pages.
  • Application password: the one you just copied.

Ghost

  1. In Ghost admin, open Settings and find Integrations (under Advanced in recent versions).
  2. Click Add custom integration, name it "pangoling", and create it.
  3. Copy the Admin API key (it looks like id:secret) and the API URL.

Paste the site URL (no trailing slash) and the Admin API key.

Webflow

  1. In Webflow, open Site settings, then Apps & integrations, then API access. Generate a token with CMS read and write access.
  2. Open your blog CMS collection and copy its Collection ID from the collection settings.
  3. Check the slug of the rich-text field your blog template renders. It is often post-body.

Fill in the collection ID, the body field slug, the site URL and the blog path segment (for example blog). If you set a summary field, it receives the meta description.

HubSpot

  1. In HubSpot, go to Settings, then Integrations, then Private Apps. Create an app with the CMS content scope and copy its access token. It starts with pat-.
  2. Find your blog ID under Settings, Website, Blog. It is the number in the address bar when you edit the blog.

Paste both.

Notion

  1. Create an internal integration at notion.so/my-integrations and copy its secret.
  2. Open the target database, click the ••• menu, then Connections, and add your integration.
  3. Copy the database ID from its URL. It is the 32-character part before any ?.

Fill in the database ID and the name of its title column. If you also give a slug column, republishing a post replaces the old page instead of creating a second one.

Shopify

Create an app for your store in Shopify that can write pages, then fill in:

  • Store domain: for example brand.myshopify.com.
  • Client ID and Client secret from that app.
  • Page template suffix (optional): use it if your theme has a separate page template for articles.

Git / Markdown (static sites)

For sites that build from Markdown files in a GitHub repository. Each article is committed as one file, and the commit triggers your normal build and deploy. Content stays versioned in your repo.

To give pangoling access, use Connect GitHub on the connection form, or Install the GitHub App where it is offered, in which case commits appear under the app's bot name. If you would rather use a token, paste one under Advanced: a fine-grained token with Contents: write on the repo, or a classic token with the repo scope.

FieldExampleWhat it is
Repoacme/websiteowner and name
Branchmainthe branch your site builds from
Content directorycontent/blog/{lang}where article files go; {lang} is replaced with the article's language
File extensionmdxmd or mdx
Frontmatter date fielddatethe key your site reads the publish date from
Date formatisoiso for a full timestamp, date for YYYY-MM-DD
Site URLhttps://example.comused to build the live link
Blog URL pathblogthe path segment articles live under; {lang} allowed
Default localeenoptional; dropped from {lang} URLs

Each file opens with frontmatter your templates can read: title, slug, lang, your date field, description, category, author, readingMinutes, tags, keywords and relatedPages. description is always present, and empty lists are written as [] rather than left out, so a template that reads tags.length does not break the build.

If one language lives under a different path, for example Swedish at sv/blogg, set per-locale path overrides as JSON: {"sv":"sv/blogg"}.

Custom site (webhook)

For a custom or database-backed site. Your developer adds one endpoint, and pangoling sends each approved article to it as JSON.

  1. Add an endpoint that checks an Authorization: Bearer <token> header and accepts a JSON body.
  2. Save the post, and reply with JSON that includes the live url.
  3. In pangoling, paste the endpoint URL and choose a long random string as the bearer token.

A minimal Next.js endpoint:

// app/api/ingest/route.ts
export async function POST(req: Request) {
  const token = req.headers.get('authorization')?.replace('Bearer ', '');
  if (token !== process.env.INGEST_TOKEN) return new Response('Unauthorized', { status: 401 });

  const body = await req.json();
  if (body.ping) return Response.json({ ok: true }); // Test connection sends this

  const { slug, title, html, markdown, metaDescription } = body;
  // Save the post in your database here.
  return Response.json({ url: `https://yoursite.com/articles/${slug}` });
}

What each article sends:

FieldTypeNotes
slugstringthe URL slug
titlestring
htmlstringthe body, rendered
markdownstringthe same body, as Markdown
metaDescriptionstring or null
languagestringfor example en
contentTypestringfor example blog, pillar, glossary
categorystring or null
targetKeywordstring or null
tagsstring array
ogImagestring or nullimage URL
authorobject or nullpresent only when bylines are on
scheduledForstring or nullISO date, only if your endpoint releases scheduled posts. Kept for compatibility: store publishedAt instead
noindexbooleantrue only for the launch check's test article
contractVersionnumber2
keywordsstring arraythe target keyword
publishedAtstring or nullISO date of the first publish. Store it on first insert and never overwrite it
updatedAtstring or nullISO modified date, set only after a real refresh. Null means never updated; do not default it to publishedAt
canonicalstring or nullcanonical URL, when the piece points at another page
authorUrlstring or nullthe "How we write" page, for the byline link
readingMinutesnumber
imageAltstring or nullalt text for a generated cover
relatedPagesarraysame-site pages the body links to, as { url, title }; always an array
entitiesarraythe same pages as { name, url }; always an array
breadcrumbsstring arraytrail entries as Name|/path, home first; may be empty
breadcrumbSchemastring or nullthe trail as a BreadcrumbList JSON string; null whenever jsonLd is sent, because the document already carries the trail
faqSchemastring or nullFAQPage JSON-LD, when the piece has an FAQ
jsonLdstring or nulla complete schema.org JSON-LD document to print in the page head
indexNowobject{ key, path }; present only when your site has a key. Serve path with the key as its body
idempotencyKeystringstable per piece and payload; also sent as the Idempotency-Key header. Drop repeats
sourcestringalways markos

Ignore any key you do not know. Prefer entities and relatedPages over text matching when you build related-content blocks.

Any 2xx response counts as published. If the response is JSON with url (and optionally id), pangoling links to the live page. The default method is POST; you can switch it to PUT.

There is no delete call in this contract, so the launch check cannot remove its test article from a webhook site. It stays with a noindex request until you delete it by hand.