Publishing to your CMS
Connect WordPress, Ghost, Webflow, GitHub and more, and publish drafts with images and metadata.
LogNorm publishes finished articles straight into your CMS: the text, the hero and body images, the SEO title and description, and the live URL it needs to start measuring the move. This page covers connecting a CMS, what gets sent, publish modes, GitHub pull requests, scheduling and what to do when a publish fails.
Publishing is a plan feature, and the number of websites that can have a CMS connected is a plan limit. If your plan doesn't include it, the publish dialog shows See plans instead. Compare plans on the pricing page.
Supported CMSs
Twelve destinations are supported, grouped in the CMS gallery as Hosted CMS, Headless, Git and Custom. Each one shows icons for what it can do.
| CMS | Modes | Images |
|---|---|---|
| WordPress | Draft, live, schedule | Uploaded to the media library; hero set as featured image |
| Ghost | Draft, live, schedule | Uploaded; hero set as feature image |
| Webflow (Beta) | Draft, live | Webflow imports them from LogNorm's URLs |
| Contentful (Beta) | Draft, live | Uploaded as assets |
| Sanity (Beta) | Draft, live | Uploaded as image assets |
| Strapi (v4 and v5) | Draft, live | Uploaded to the media library |
| HubSpot CMS (Beta) | Draft, live, schedule | Uploaded to the File Manager |
| Shopify (Beta) | Draft, live, schedule | Hero uploaded; body images linked |
| Storyblok (Beta) | Draft, live | Uploaded as assets |
| Notion (Beta) | Draft, live | Notion imports them from LogNorm's URLs |
| GitHub | Pull request | Committed to the repository in the same pull request |
| Webhook | Draft, live | Image URLs included in the JSON payload |
Every destination except the webhook updates in place: publishing a piece again edits the same remote post rather than creating a duplicate.
Integrations marked Beta are built to the CMS's official API and tested, but not yet verified against a live account. You can use them normally. If one fails, LogNorm records the details (never your credentials) so the team can fix it.
One CMS per website, one website per CMS
Each connection belongs to exactly one website, and the rule works in both directions:
- A website has at most one CMS. To switch CMS, disconnect the current one first.
- A CMS serves at most one website in your workspace, so an article for one site can never land in another site's CMS.
"The same CMS" means the same WordPress site, Ghost site, Webflow collection, Contentful space and environment, Sanity project and dataset, Strapi URL and collection, HubSpot blog, Shopify store, Storyblok space, Notion database, GitHub repository and content folder, or webhook URL. Two sites in one GitHub monorepo can each connect, as long as they use different content folders. In the wizard, collections, blogs and databases that another website already uses show as Used by that website and can't be picked.
If a CMS publishes to a host that isn't your website's domain, the connection card shows a warning ("This CMS publishes to X, not your domain"). Publishing still works; it's a prompt to check you connected the right CMS. If that host is another website in your workspace, the connection is refused.
Connect your CMS
Connections are managed per website in SettingsIntegrations. You need the Owner or Admin role to connect, edit or disconnect a CMS. Make sure the website you want is selected before you start.
- Pick your CMS
Open SettingsIntegrations and choose your CMS in the Connect a CMS gallery. A guided setup opens.
- Credentials
Enter the fields your CMS asks for (see the table below). Secrets are encrypted for your workspace and never shown again; the connection card only displays the last few characters.
- Test
LogNorm checks the credentials and permissions against your CMS. Nothing is created. You see Connection works or Connection failed with the CMS's own message.
- Destination
Where the CMS has several places to publish, pick one from the live list: a post type, collection, content type, blog or database. If the list is empty, create one in the CMS and reload.
- Mapping (or Settings)
For CMSs with your own schema, map LogNorm's fields (title, slug, body, excerpt, hero image, SEO title and description, date) to the remote fields. Then choose the default for publishing (Save as a draft or Publish live), how images are handled, and CMS-specific options such as author, categories, tags or locale.
- Save
Name the destination (this is how it appears in the publish dialog) and click Save connection. LogNorm tests it once more. If the test fails, the connection is still saved and marked Failing so you can fix it later.
What each CMS needs
| CMS | Credentials |
|---|---|
| WordPress | Site URL, a username with the Editor or Author role, and an Application password (Users › Profile › Application Passwords). Self-hosted or WordPress.com Business, over HTTPS. |
| Ghost | Admin API URL and Admin API key from Settings › Integrations › Add custom integration. The key looks like id:secret. |
| Webflow | Site API token with CMS read/write and Sites read. |
| Contentful | Space ID, environment and a content management token. |
| Sanity | Project ID, dataset and an API token with Editor rights. |
| Strapi | Strapi URL (without /api), the collection's plural API ID, and an API token that can create, update and find on the collection and upload files. |
| HubSpot CMS | Private app access token with the content and files scopes. Pick a blog author: HubSpot requires one to publish live. |
| Shopify | Your .myshopify.com domain (not the custom domain) and an Admin API access token with write_content. |
| Storyblok | Space ID, region and a personal access token. |
| Notion | Internal integration secret. Share your database with the integration (••• › Connections) or it won't appear. |
| GitHub | Owner, repository, optional base branch, and a fine-grained personal access token for that repository only, with Contents: write and Pull requests: write. |
| Webhook | An HTTPS endpoint URL. LogNorm generates the signing secret for you. |
Contentful, Sanity, Strapi, Storyblok, Notion, Webflow, GitHub and webhooks don't tell LogNorm the live address of a post. Set Public URL pattern (for example https://acme.com/blog/{slug}) so LogNorm records the live URL and starts measuring the move once the piece is published.
Match Body format to your field type: Rich text (Blocks) for a Blocks field, Markdown for a Rich text (Markdown) field. A mismatch, or a mapping to a field that doesn't exist, makes Strapi reject the publish. Blocks have no table type, so tables become one paragraph per row.
Managing a connection
The connection card shows its status (Connected, Failing or Not tested), the destination, the default publish mode, image handling and the number of publications. Use Re-test to check it again, Edit to change settings, and the ⋯ menu to disconnect. Disconnecting deletes the encrypted credentials and this destination's publication history in LogNorm; content already published stays live in your CMS. For webhooks, the menu also has Show signing secret and Rotate signing secret.
What gets sent
- Title, slug and body. The body is converted to what your CMS expects: HTML, Markdown, Portable Text, Strapi Blocks, Contentful rich text, Storyblok rich text or Notion blocks.
- Metadata. The SEO title and meta description go to the CMS fields made for them where they exist (for example Ghost's meta fields, HubSpot's HTML title and meta description, or the fields you mapped on a headless CMS). WordPress receives the description as the excerpt. The target keyword is added as a tag on WordPress.
- Images. The hero and every body image, handled per the connection's image setting: Upload to the CMS, Link LogNorm-hosted images or Leave images out. If the CMS has no featured-image slot, or you didn't map one, the hero is placed at the top of the body.
- Schema. The article's structured data is included in the webhook payload. Other CMSs don't receive it; add schema in your theme or templates.
The publish dialog shows what will happen to the images before you send (for example "Hero + 3 body images"), with a Change link to the connection settings.
Publish a piece
A piece must be approved before it can go live (see the review workflow in Content and the editor). Publishing requires the Owner, Admin or Editor role.
- Open the publish dialog
In the piece, click Publish (or Publish update once it's live). You can also drag the piece to Published on the content board.
- Choose the destination and mode
Pick your connected CMS and a mode:
Mode What happens Save as draft Lands in the CMS unpublished for a final check. The piece and its move keep their status. Publish live Goes live now. The piece and its move become Published and measurement starts. Schedule The CMS publishes it at the piece's planned date. Pull request GitHub only. Opens a pull request; merging it marks the move published. - Send it
Click Publish, Send draft, Schedule or Open pull request. Progress shows in the dialog, then a link to the live post, the draft or the pull request.
Published it yourself? In the same dialog, enter the live URL and click Mark published so the move still gets measured. See Results and measurement.
On WordPress, Ghost, Shopify, Notion and Strapi v4, saving a draft of a live post takes the live post down. Once a piece is live there, LogNorm turns off Save as draft and Schedule for it. Use Publish live to update it in place.
Scheduling
Scheduling is available on WordPress, Ghost, HubSpot and Shopify. It uses the piece's planned publish date, which you set in the Blog planner. The date must be at least a minute in the future. The CMS publishes the post at that time, and LogNorm marks the piece and move published within about 15 minutes after.
GitHub pull requests
GitHub is for sites built from a repository. It's a separate plan feature from publishing. Each publish creates a branch named lognorm/<slug>-… and opens a pull request against your base branch containing:
- The article at
<Content path>/<slug>.mdx(or.md), with frontmatter from your Frontmatter template. Variables:{{title}},{{description}},{{slug}},{{date}},{{keyword}},{{meta_title}},{{content_type}},{{image}},{{image_alt}}. - The images, committed to
<Images folder>/<slug>/and referenced through your Images public path. - The SEO title and description in the pull request description, for your reviewer.
LogNorm checks open pull requests every 15 minutes. When yours is merged, the piece and move are marked published. If it's closed without merging, the publication is marked failed. To skip the wait, click Mark merged in Recent publications. Publishing again after a merge opens an "Update:" pull request that edits the existing file.
Webhooks
For a custom CMS or pipeline, LogNorm sends a signed JSON POST to your endpoint. LogNorm generates the signing secret and saves it with the connection the first time you test it, so you can stop half way: put the secret in your endpoint, deploy, then come back and use Re-test. Show signing secret in the connection's menu shows it again. Verify the X-LogNorm-Signature header (sha256= plus an HMAC-SHA256 of the raw body) on every request. Reply 200 with { "id": "...", "url": "https://…" } to give LogNorm the remote ID and the live URL. The full payload and a verification snippet are in Webhook payload & signature on the Integrations page.
Failures and retry
Every push is listed in Recent publications on SettingsIntegrations, with its status: Publishing, Draft in CMS, Scheduled, PR open, Published, Failed or Superseded. A draft can be sent live from there with Publish live. The publish dialog also shows the piece's history.
When a publish fails, the error is recorded on the publication and the connection, and your team is notified. LogNorm never retries automatically, because a blind retry could create a duplicate post. Fix the cause, then click Retry in Recent publications or Try again in the dialog.
| Message or symptom | What to do |
|---|---|
| This website already publishes to X | One CMS per website. Disconnect the current one to switch. |
| X is already connected to another website | Disconnect it there, or connect a different site, collection or folder. |
| That address is on a private network | LogNorm only connects to public addresses. Use your CMS's public URL. |
| WordPress 401 | Wrong username or application password, or a security plugin blocks the REST API or basic auth. |
| Ghost says the key must look like id:secret | You pasted the Content API key or half of the Admin API key. |
| Strapi 400 on publish | A mapped field doesn't exist, or the Body format doesn't match the field type. |
| GitHub cannot push | The token lacks Contents: write or Pull requests: write, or isn't scoped to this repository. |
| Approve the piece before publishing it live | Get it approved, or send it as a draft. |