Turns your content plan into blog posts: you approve the outline, then the finished post, before anything is published on WordPress.

UsesGoogle SheetsOpenAIWordPress

Write blog posts from your content plan, with your sign-off

ScheduleRun every weekday at 08:00.Run every weekday at 08:00.
ValuesSet spreadsheetUrl to blank.Set spreadsheetUrl to blank.
ValuesSet planTab to blank.Set planTab to blank.
ValuesSet recordTab to blank.Set recordTab to blank.
ValuesSet houseStyle to blank.Set houseStyle to blank.
ValuesSet doneTopics to an empty list.Set doneTopics to an empty list.
Decisions & LoopsIf a step below fails, save the error as recordUnreadableIf a step below fails, save the error as recordUnreadable
Try
ValuesChange doneTopics to the values from the Google Sheets “Read column” step.Change doneTopics to the values from the Google Sheets “Read column” step.
On error
Decisions & LoopsStop and report a problem: The '@recordTab' tab could not be read, so there is no way to tell which topics have already been through review. Nothing was written. Create the tab with these headers in row 1 - Topic, Recorded on, Decision, Post ID, Link, Your note - then run it again.Stop and report a problem: The '@recordTab' tab could not be read, so there is no way to tell which topics have already been through review. Nothing was written. Create the tab with these headers in row 1 - Topic, Recorded on, Decision, Post ID, Link, Your note - then run it again.
ValuesSet plan to nothing.Set plan to nothing.
Decisions & LoopsIf a step below fails, save the error as planUnreadableIf a step below fails, save the error as planUnreadable
Try
Google SheetsGoogle SheetsRead the rows of spreadsheetUrl. Save it as plan.Read the rows of spreadsheetUrl. Save it as plan.
On error
Decisions & LoopsStop and report a problem: The '@planTab' tab could not be read: planUnreadable as text.Stop and report a problem: The '@planTab' tab could not be read: planUnreadable as text.
ValuesSet planRows to plan.rows.Set planRows to plan.rows.
ValuesSet topic to blank.Set topic to blank.
ValuesSet angle to blank.Set angle to blank.
Decisions & LoopsFor each planRow in planRows:For each planRow in planRows:
ValuesSet candidateTopic to blank.Set candidateTopic to blank.
Decisions & LoopsIf a step below fails, save the error as noTopicCellIf a step below fails, save the error as noTopicCell
Try
TextTrim planRow.Topic and save it as candidateTopic.Trim planRow.Topic and save it as candidateTopic.
On error
ValuesChange candidateTopic to blank.Change candidateTopic to blank.
Decisions & LoopsIf candidateTopic is nullIf candidateTopic is null
Yes
ValuesChange candidateTopic to blank.Change candidateTopic to blank.
No
No steps
Decisions & LoopsIf candidateTopic is emptyIf candidateTopic is empty
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf doneTopics contains candidateTopicIf doneTopics contains candidateTopic
Yes
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesChange topic to candidateTopic.Change topic to candidateTopic.
Decisions & LoopsIf a step below fails, save the error as noAngleCellIf a step below fails, save the error as noAngleCell
Try
TextTrim planRow.Angle and save it as angle.Trim planRow.Angle and save it as angle.
On error
ValuesChange angle to blank.Change angle to blank.
Decisions & LoopsIf angle is nullIf angle is null
Yes
ValuesChange angle to blank.Change angle to blank.
No
No steps
Decisions & LoopsStop repeating.Stop repeating.
Decisions & LoopsIf topic is emptyIf topic is empty
Yes
Decisions & LoopsFinish the run: Every topic on '@planTab' has already been through review. Add a row to the plan and the next run will pick it up.Finish the run: Every topic on '@planTab' has already been through review. Add a row to the plan and the next run will pick it up.
No
No steps
Dates & TimesGet today's date and save it as recordedOn.Get today's date and save it as recordedOn.
ValuesSet outlinePrompt to Plan one blog post. Reply with a title line, then four to six section headings, each with one sentence saying what the section will argue. Reply with the outline only - no preamble, and do not write the post itself.Set outlinePrompt to Plan one blog post. Reply with a title line, then four to six section headings, each with one sentence saying what the section will argue. Reply with the outline only - no preamble, and do not write the post itself. House style: houseStyle Topic: topic Angle the writer asked for: angle.
ValuesSet outline to blank.Set outline to blank.
Decisions & LoopsIf a step below fails, save the error as outlineErrorIf a step below fails, save the error as outlineError
Try
TextTrim the response text from the OpenAI “Ask OpenAI” step and save it as outline.Trim the response text from the OpenAI “Ask OpenAI” step and save it as outline.
On error
ValuesShow in the output: The outline for '@topic' could not be written: outlineError as text.Show in the output: The outline for '@topic' could not be written: outlineError as text.
ValuesChange outline to blank.Change outline to blank.
Decisions & LoopsIf the length of outline is less than 80If the length of outline is less than 80
Yes
Decisions & LoopsStop and report a problem: No usable outline came back for '@topic', so nothing was written, recorded or published. The topic stays on the plan for the next run.Stop and report a problem: No usable outline came back for '@topic', so nothing was written, recorded or published. The topic stays on the plan for the next run.
No
No steps
ValuesSet claimRecordRow to [topic, recordedOn, Outline - awaiting approval, , , ].Set claimRecordRow to [topic, recordedOn, Outline - awaiting approval, , , ].
Google SheetsGoogle SheetsAdd a row to recordTab in spreadsheetUrl.Add a row to recordTab in spreadsheetUrl.
ValuesSet outlineAnswer to blank.Set outlineAnswer to blank.
ValuesSet outlineApproved to false.Set outlineApproved to false.
ValuesSet outlineDecision to Outline - no answer.Set outlineDecision to Outline - no answer.
Decisions & LoopsIf a step below fails, save the error as outlineApprovalErrorIf a step below fails, save the error as outlineApprovalError
Try
ApprovalsRequest approval: Approve the outline for @topic? Save it as outlineReview.Request approval: Approve the outline for @topic? Save it as outlineReview.Approve and the post is written to this outline and brought back to you before anything reaches WordPress. Reject it and nothing is written: the topic is closed, any comment you leave is kept on the record tab, and the topic is not offered again. To try it again, delete its rows...
Decisions & LoopsIf outlineReview.approved is yesIf outlineReview.approved is yes
Yes
ValuesChange outlineApproved to true.Change outlineApproved to true.
Else if @outlineReview.timed_out is no
ValuesChange outlineDecision to Outline - rejected.Change outlineDecision to Outline - rejected.
Otherwise
No steps
TextTrim outlineReview.comment and save it as outlineAnswer.Trim outlineReview.comment and save it as outlineAnswer.
On error
ValuesShow in the output: The outline review for '@topic' could not be sent: outlineApprovalError as text.Show in the output: The outline review for '@topic' could not be sent: outlineApprovalError as text.
Decisions & LoopsIf outlineApproved is noIf outlineApproved is no
Yes
ValuesSet outlineRecordRow to [topic, recordedOn, outlineDecision, , , outlineAnswer].Set outlineRecordRow to [topic, recordedOn, outlineDecision, , , outlineAnswer].
Google SheetsGoogle SheetsAdd a row to recordTab in spreadsheetUrl.Add a row to recordTab in spreadsheetUrl.
Decisions & LoopsFinish the run: The outline for '@topic' was not approved, so nothing was written and nothing reached WordPress. The topic is closed and your answer is on the '@recordTab' tab. To have another go at it, delete its rows from the '@recordTab' tab.Finish the run: The outline for '@topic' was not approved, so nothing was written and nothing reached WordPress. The topic is closed and your answer is on the '@recordTab' tab. To have another go at it, delete its rows from the '@recordTab' tab.
No
No steps
ValuesSet draftPrompt to Write the blog post below to the outline you are given. Follow the outline's sections in order. Reply with the post body only - no title line, no commentary. Write it as HTML for WordPress: <h2> for each section heading, <p> for paragraphs, <ul><li> for lists and <strong> for emphasis. Use no Markdown (no #, ** or -) and do not wrap the HTML in a code block. Do not state a statistic, price, date or named source unless it is in the brief.Set draftPrompt to Write the blog post below to the outline you are given. Follow the outline's sections in order. Reply with the post body only - no title line, no commentary. Write it as HTML for WordPress: <h2> for each section heading, <p> for paragraphs, <ul><li> for lists and <strong> for emphasis. Use no Markdown (no #, ** or -) and do not wrap the HTML in a code block. Do not state a statistic, price, date or named source unless it is in the brief. House style: houseStyle Topic: topic Approved outline: outline.
ValuesSet draft to blank.Set draft to blank.
Decisions & LoopsIf a step below fails, save the error as draftErrorIf a step below fails, save the error as draftError
Try
TextTrim the response text from the OpenAI “Ask OpenAI” step and save it as draft.Trim the response text from the OpenAI “Ask OpenAI” step and save it as draft.
On error
ValuesShow in the output: The post for '@topic' could not be written: draftError as text.Show in the output: The post for '@topic' could not be written: draftError as text.
ValuesChange draft to blank.Change draft to blank.
TextReplace the pattern ^\s*```[a-zA-Z]*\s*|\s*```\s*$ with in draft and save it as draft.Replace the pattern ^\s*```[a-zA-Z]*\s*|\s*```\s*$ with in draft and save it as draft.
Decisions & LoopsIf the length of draft is less than 400If the length of draft is less than 400
Yes
ValuesSet shortDraftRecordRow to [topic, recordedOn, Draft too short - nothing sent to WordPress, , , ].Set shortDraftRecordRow to [topic, recordedOn, Draft too short - nothing sent to WordPress, , , ].
Google SheetsGoogle SheetsAdd a row to recordTab in spreadsheetUrl.Add a row to recordTab in spreadsheetUrl.
Decisions & LoopsStop and report a problem: The draft for '@topic' came back too short to be a post, so nothing was sent to WordPress.Stop and report a problem: The draft for '@topic' came back too short to be a post, so nothing was sent to WordPress.
No
No steps
TextReplace the pattern (?i)(\s*(</(p|h[1-6]|li|ul|ol|blockquote)\s*>|<br\s*/?>)\s*)+ with "Replace the pattern (?i)(\s*(</(p|h[1-6]|li|ul|ol|blockquote)\s*>|<br\s*/?>)\s*)+ with in draft and save it as draftText.
TextReplace the pattern (?i)<li[^>]*> with - in draftText and save it as draftText.Replace the pattern (?i)<li[^>]*> with - in draftText and save it as draftText.
TextReplace the pattern <[^>]+> with in draftText and save it as draftText.Replace the pattern <[^>]+> with in draftText and save it as draftText.
TextMake draftText back from HTML and save it as draftText.Make draftText back from HTML and save it as draftText.
ValuesSet publishAnswer to blank.Set publishAnswer to blank.
ValuesSet postStatus to draft.Set postStatus to draft.
Decisions & LoopsIf a step below fails, save the error as publishApprovalErrorIf a step below fails, save the error as publishApprovalError
Try
ApprovalsRequest approval: Publish @topic on your site? Save it as publishReview.Request approval: Publish @topic on your site? Save it as publishReview.Approve and it goes live on your site. Reject it, or leave it, and it is saved in WordPress as a draft for you to finish by hand. draftText
Decisions & LoopsIf publishReview.approved is yesIf publishReview.approved is yes
Yes
ValuesChange postStatus to publish.Change postStatus to publish.
No
No steps
TextTrim publishReview.comment and save it as publishAnswer.Trim publishReview.comment and save it as publishAnswer.
On error
ValuesShow in the output: The publish review for '@topic' could not be sent, so it will be saved as a draft: publishApprovalError as text.Show in the output: The publish review for '@topic' could not be sent, so it will be saved as a draft: publishApprovalError as text.
ValuesSet createdPost to nothing.Set createdPost to nothing.
Decisions & LoopsIf a step below fails, save the error as wordpressErrorIf a step below fails, save the error as wordpressError
Try
WordPressWordPressCreate the post topic. Save it as createdPost.Create the post topic. Save it as createdPost.draft
On error
ValuesShow in the output: WordPress refused the post for '@topic': wordpressError as text.Show in the output: WordPress refused the post for '@topic': wordpressError as text.
ValuesShow in the output: draft.Show in the output: draft.
ValuesSet refusedRecordRow to [topic, recordedOn, WordPress refused the post - nothing created, , , wordpressError.message].Set refusedRecordRow to [topic, recordedOn, WordPress refused the post - nothing created, , , wordpressError.message].
Decisions & LoopsIf a step below fails, save the error as refusedRecordErrorIf a step below fails, save the error as refusedRecordError
Try
Google SheetsGoogle SheetsAdd a row to recordTab in spreadsheetUrl.Add a row to recordTab in spreadsheetUrl.
On error
ValuesShow in the output: The outcome for '@topic' could not be recorded on the '@recordTab' tab: refusedRecordError as text.Show in the output: The outcome for '@topic' could not be recorded on the '@recordTab' tab: refusedRecordError as text.
Decisions & LoopsStop and report a problem: WordPress would not take the post for '@topic', so nothing was created there. The draft is kept in this run's output. To have another go, delete the topic's rows from the '@recordTab' tab.Stop and report a problem: WordPress would not take the post for '@topic', so nothing was created there. The draft is kept in this run's output. To have another go, delete the topic's rows from the '@recordTab' tab.
ValuesSet postId to blank.Set postId to blank.
Decisions & LoopsIf a step below fails, save the error as noPostIdIf a step below fails, save the error as noPostId
Try
TextTrim createdPost.id and save it as postId.Trim createdPost.id and save it as postId.
On error
ValuesChange postId to blank.Change postId to blank.
Decisions & LoopsIf postId is nullIf postId is null
Yes
ValuesChange postId to blank.Change postId to blank.
No
No steps
ValuesSet postLink to blank.Set postLink to blank.
Decisions & LoopsIf a step below fails, save the error as noPostLinkIf a step below fails, save the error as noPostLink
Try
TextTrim createdPost.link and save it as postLink.Trim createdPost.link and save it as postLink.
On error
ValuesChange postLink to blank.Change postLink to blank.
Decisions & LoopsIf postLink is nullIf postLink is null
Yes
ValuesChange postLink to blank.Change postLink to blank.
No
No steps
ValuesSet finalDecision to Saved in WordPress as a draft.Set finalDecision to Saved in WordPress as a draft.
Decisions & LoopsIf postStatus is publishIf postStatus is publish
Yes
ValuesChange finalDecision to Published.Change finalDecision to Published.
No
No steps
ValuesSet publishedRecordRow to [topic, recordedOn, finalDecision, postId, postLink, publishAnswer].Set publishedRecordRow to [topic, recordedOn, finalDecision, postId, postLink, publishAnswer].
Google SheetsGoogle SheetsAdd a row to recordTab in spreadsheetUrl.Add a row to recordTab in spreadsheetUrl.
ValuesShow in the output: '@topic' - finalDecision. postLink.Show in the output: '@topic' - finalDecision. postLink.