--- name: clickfunnels-import-external-blog-post description: > Copy one or more selected articles from an RSS feed, Atom feed, or public web page into an existing ClickFunnels blog as verified drafts. Use when migrating external blog content while preserving source metadata and avoiding duplicate posts. version: "1.0" author: ClickFunnels tools: - http references: - path: https://accounts.myclickfunnels.com/llms.txt description: Quick-reference index for the ClickFunnels API. - path: https://accounts.myclickfunnels.com/.well-known/blogs/skill.md description: Blog and blog-post endpoint reference, fields, and publication rules. - path: https://accounts.myclickfunnels.com/.well-known/page-markup/skill.md description: PML reference for the markup field used by blog posts. - path: https://accounts.myclickfunnels.com/.well-known/pages/skill.md description: Page API reference for template-level custom CSS and head code. --- # Import an External Blog Post This workflow copies one or more articles from an external site into an existing ClickFunnels blog. It discovers the source content, converts HTML into valid PML, creates drafts one at a time, and verifies each result before publication. Import means copy. The ClickFunnels API does not modify, redirect, unpublish, or delete the source article. ## When to use this skill - Import the newest article from an RSS or Atom feed - Copy a specific public article into a ClickFunnels blog - Migrate the exact remaining entries from a fixed feed snapshot - Preserve an original title, excerpt, and publication timestamp - Run an idempotent migration without creating duplicate posts - Review migrated content as a draft before publishing it ## Authentication Send a ClickFunnels OAuth access token on every ClickFunnels API request: ```http Authorization: Bearer ``` The public source feed or article usually does not require ClickFunnels authentication. If the source is private, obtain separate permission and credentials for that source. ## Endpoint reference | Action | Method | Path | |--------|--------|------| | List destination blogs | GET | `/api/v2/workspaces/:workspace_id/blogs` | | List existing posts | GET | `/api/v2/blogs/:blog_id/posts` | | Create a draft | POST | `/api/v2/blogs/:blog_id/posts` | | Verify a post and its markup | GET | `/api/v2/blogs/posts/:id?expand[]=markup` | | Publish a verified draft | PATCH | `/api/v2/blogs/posts/:id` | | Delete a confirmed destination post | DELETE | `/api/v2/blogs/posts/:id` | | Style a destination template page | PATCH | `/api/v2/pages/:id` | ## Safety rules 1. Create with `visibility: "draft"` unless the user explicitly requests immediate publication. 2. Do not send a `slug` field. ClickFunnels derives the post path from `title`. 3. Do not delete or redirect the source article as part of this workflow. 4. Do not publish until metadata, markup, links, and images have been verified. 5. Treat source HTML as untrusted input. Normalizing HTML is not sanitizing it; remove or explicitly review executable content before importing it. 6. Treat draft as an editorial state, not a confidentiality boundary. Do not put secrets in a draft post. 7. Treat a shared blog template change as a separate operation. Require explicit approval for the exact template Page and code change before updating it. 8. Import into a new post. This workflow only ever creates posts, which is safe: there is no existing body to lose. The moment you instead write `markup` to a post that already exists, you are replacing its whole body with a PML subset of what the editor can build, so require explicit approval first. See [STOP: get approval before overwriting an existing page](https://accounts.myclickfunnels.com/.well-known/page-markup/skill.md#stop-get-approval-before-overwriting-an-existing-page). ## 1. Discover the source feed When given a site or article URL, fetch its HTML and inspect the document head for an alternate feed link: ```html ``` Resolve a relative `href` against the page URL. If no alternate link exists, look for a feed URL advertised by the site. Common paths such as `/feed/`, `/rss.xml`, and `/atom.xml` are useful probes, but do not assume that one is authoritative without checking its contents. If there is no usable feed, accept a specific article URL and extract the main article body from that page. Avoid importing navigation, cookie banners, related-post cards, or the site footer. ## 2. Select the latest article Do not assume feed order is chronological. Parse every candidate item and sort by its best available timestamp: - RSS: `pubDate`, then a namespaced date field - Atom: `published`, then `updated` - Missing dates: use feed order and report that the date could not be verified For the selected item, capture: - Stable source key: canonical article URL, falling back to the feed GUID or ID - Title - Canonical article URL - Publication timestamp - Excerpt or summary - Full article HTML For RSS, prefer `content:encoded` over `description`. For Atom, prefer `content` over `summary`. Decode the feed's CDATA or escaped HTML before the next step. Do not nest the source CDATA wrapper inside the PML wrapper. Some feeds contain only a summary. In that case, fetch the canonical article URL and extract its article body. If a complete article cannot be identified reliably, stop before creating a destination post. ## 3. Normalize external HTML for PML The `markup` field accepts PML, not an arbitrary HTML document. For a faithful one-post migration, place the normalized article body in a top-level `` element: ```xml

A practical migration

Verify the draft before publishing it.

Notebook]]>
``` Although CDATA protects the HTML while PML is parsed, the custom HTML is parsed again when the page tree is built. The content must therefore be well-formed XHTML. Use an HTML parser and serializer rather than regular expressions: - Close every non-void element - Self-close void elements such as `br`, `hr`, `img`, `input`, and `source` - Quote every attribute value - Serialize true HTML boolean attributes as repeated values, such as `async="async"` or `disabled="disabled"` - Serialize a non-boolean valueless attribute as an empty value, such as `data-note=""`, or remove it - Escape bare ampersands and invalid text characters - Remove malformed closing tags - Rewrite relative `href`, `src`, and `srcset` values as absolute URLs - Remove scripts, inline event handlers, forms, and other executable content unless the user explicitly wants a reviewed embed preserved - Replace each literal `]]>` with `]]]]>` before wrapping it `customhtml` preserves source links and can also preserve scripts and embed code. The normalization step does not make that code safe or inert. Strip it by default and keep only code whose behavior and external requests were reviewed. Choose the representation based on the content: - Prefer native PML elements for simple headings, paragraphs, lists, links, and images that should remain easy to edit in ClickFunnels. - For fidelity-heavy HTML, inline links, or reviewed embeds, use one stable wrapper inside `customhtml` and scope the imported styles to that wrapper. ### Rendering fidelity Preserving the complete HTML structure does not preserve the source site's appearance. The source theme CSS is usually absent, while destination theme styles and resets can change typography, spacing, links, media, and embeds. Include explicit, wrapper-scoped CSS for the imported article when fidelity matters: ```xml .imported-article { font: 400 18px/1.7 sans-serif; } .imported-article h2 { margin: 2rem 0 0.75rem; } .imported-article p { margin: 0 0 1.25rem; } .imported-article a { color: #2457d6; text-decoration: underline; } .imported-article img { display: block; max-width: 100%; height: auto; } .imported-article blockquote { margin: 1.5rem 0; padding-left: 1rem; border-left: 4px solid #cccccc; } .imported-article iframe { display: block; width: 100%; max-width: 100%; }
...
]]>
``` Use a wrapper name that is stable and specific to the imported content. Style headings, paragraphs, lists, links, images, blockquotes, tables, and embeds that the article actually contains. Remove empty source placeholders during normalization; if a runtime placeholder must remain, hide it with a stable, scoped selector. Deduplicate reviewed external embed loaders so each script URL is loaded at most once. Scope responsive height or aspect-ratio rules to a reviewed media provider or an explicit importer-owned class, not every iframe under a generic embed-card class. Third-party loaders can inject frames that calculate their own height; wait for each loader to settle during rendered verification and confirm its frame is not clipped. An API round trip proves that markup parsed, not that it renders well. Inspect the actual rendered post at desktop and narrow widths. Check typography, spacing, link behavior, image sizing, blockquotes, and every retained embed. ### Blog template styling is separate The post `markup` field controls the article body. Stock blog-template chrome, including sidebars and template placeholders, lives outside that body on the blog template Page. If those elements need styling or empty-state cleanup, they can be updated through that template Page's `custom_css` or `head_code` using the [Update Page API](https://developers.myclickfunnels.com/reference/updatepages.md). Template Pages are shared presentation. This mutation is not part of the normal post import. Before making it: 1. Obtain separate, explicit approval for the proposed shared-template change. 2. Show and confirm the exact template Page `id` and name, and verify that it is the selected blog-post template for the destination. If its identity cannot be proven, stop and leave the template unchanged. 3. Read the existing `custom_css` and `head_code`; do not append a duplicate style block or external loader. 4. Use stable, narrowly scoped selectors and additive mode. Set `custom_css_mode: "append"` or `head_code_mode: "append"` explicitly. 5. Capture a rendered baseline and confirm that the imported post and an existing post using the template can be checked after the change. 6. Update only the approved code field. Never include the template's `markup` in this request. Use the Page Editor, rather than generated CSS text or DOM-rewriting JavaScript, for semantic template content such as headings, biographies, CTA copy, links, and images. `append` adds the new value after the current value and is the default for both fields. `replace` overwrites the entire field, so do not use it in this import workflow. `custom_css` is raw CSS without a `