import { ParagraphAPI } from "@paragraph-com/sdk"
const api = new ParagraphAPI({ apiKey: "your-api-key" })
await api.posts.update({
slug: "my-first-post",
title: "Updated Title",
markdown: "## New content"
})curl --request PUT \
--url https://public.api.paragraph.com/api/v1/posts/slug/{slug} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"markdown": "<string>",
"bodyJson": "<string>",
"title": "<string>",
"subtitle": "<string>",
"slug": "<string>",
"postPreview": "<string>",
"categories": [
"<string>"
],
"authorIds": [
"<string>"
],
"scheduledAt": 1,
"sendNewsletter": true,
"canonicalUrl": "<string>",
"publishOnline": true,
"communityId": "<string>",
"publishedAt": 1,
"imageUrl": "<string>",
"clearImage": true,
"imageAlt": "<string>"
}
'import requests
url = "https://public.api.paragraph.com/api/v1/posts/slug/{slug}"
payload = {
"markdown": "<string>",
"bodyJson": "<string>",
"title": "<string>",
"subtitle": "<string>",
"slug": "<string>",
"postPreview": "<string>",
"categories": ["<string>"],
"authorIds": ["<string>"],
"scheduledAt": 1,
"sendNewsletter": True,
"canonicalUrl": "<string>",
"publishOnline": True,
"communityId": "<string>",
"publishedAt": 1,
"imageUrl": "<string>",
"clearImage": True,
"imageAlt": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
markdown: '<string>',
bodyJson: '<string>',
title: '<string>',
subtitle: '<string>',
slug: '<string>',
postPreview: '<string>',
categories: ['<string>'],
authorIds: ['<string>'],
scheduledAt: 1,
sendNewsletter: true,
canonicalUrl: '<string>',
publishOnline: true,
communityId: '<string>',
publishedAt: 1,
imageUrl: '<string>',
clearImage: true,
imageAlt: '<string>'
})
};
fetch('https://public.api.paragraph.com/api/v1/posts/slug/{slug}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://public.api.paragraph.com/api/v1/posts/slug/{slug}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'markdown' => '<string>',
'bodyJson' => '<string>',
'title' => '<string>',
'subtitle' => '<string>',
'slug' => '<string>',
'postPreview' => '<string>',
'categories' => [
'<string>'
],
'authorIds' => [
'<string>'
],
'scheduledAt' => 1,
'sendNewsletter' => true,
'canonicalUrl' => '<string>',
'publishOnline' => true,
'communityId' => '<string>',
'publishedAt' => 1,
'imageUrl' => '<string>',
'clearImage' => true,
'imageAlt' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://public.api.paragraph.com/api/v1/posts/slug/{slug}"
payload := strings.NewReader("{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://public.api.paragraph.com/api/v1/posts/slug/{slug}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://public.api.paragraph.com/api/v1/posts/slug/{slug}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}Update a post by slug
Update an existing post using its URL slug. The publication is identified by the API key provided in the Authorization header.
Behavior:
- Only provided fields are updated; omitted fields remain unchanged
- When
markdownis provided, it replaces the full content. Rich blocks (embeds, buttons, callouts) created in the editor will be lost - Set
statusto"published"to publish a draft,"draft"to unpublish, or"archived"to archive - When editing an already-live post that should remain live, include
status: "published"in the update and verify the returned post status before telling the writer it is live - Set
scheduledAt(Unix timestamp in milliseconds) to schedule a draft’s first-publish for a future time. Must be in the future and at most 30 days out. Only valid for posts that haven’t been published or already scheduled. PassscheduledAt: nullto cancel a previously scheduled publish (or to reschedule: cancel first, then schedule again with the new time). SetsendNewsletter: truealongsidescheduledAtto email subscribers when the post publishes. - Set
imageUrlto update the post’s cover/hero image; the URL is fetched, re-hosted, and a placeholder is generated. PassclearImage: trueto remove the existing cover. - Set
imageAltto describe the cover image for screen readers. It renders everywhere the cover appears, so set it whenever you setimageUrl.
import { ParagraphAPI } from "@paragraph-com/sdk"
const api = new ParagraphAPI({ apiKey: "your-api-key" })
await api.posts.update({
slug: "my-first-post",
title: "Updated Title",
markdown: "## New content"
})curl --request PUT \
--url https://public.api.paragraph.com/api/v1/posts/slug/{slug} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"markdown": "<string>",
"bodyJson": "<string>",
"title": "<string>",
"subtitle": "<string>",
"slug": "<string>",
"postPreview": "<string>",
"categories": [
"<string>"
],
"authorIds": [
"<string>"
],
"scheduledAt": 1,
"sendNewsletter": true,
"canonicalUrl": "<string>",
"publishOnline": true,
"communityId": "<string>",
"publishedAt": 1,
"imageUrl": "<string>",
"clearImage": true,
"imageAlt": "<string>"
}
'import requests
url = "https://public.api.paragraph.com/api/v1/posts/slug/{slug}"
payload = {
"markdown": "<string>",
"bodyJson": "<string>",
"title": "<string>",
"subtitle": "<string>",
"slug": "<string>",
"postPreview": "<string>",
"categories": ["<string>"],
"authorIds": ["<string>"],
"scheduledAt": 1,
"sendNewsletter": True,
"canonicalUrl": "<string>",
"publishOnline": True,
"communityId": "<string>",
"publishedAt": 1,
"imageUrl": "<string>",
"clearImage": True,
"imageAlt": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
markdown: '<string>',
bodyJson: '<string>',
title: '<string>',
subtitle: '<string>',
slug: '<string>',
postPreview: '<string>',
categories: ['<string>'],
authorIds: ['<string>'],
scheduledAt: 1,
sendNewsletter: true,
canonicalUrl: '<string>',
publishOnline: true,
communityId: '<string>',
publishedAt: 1,
imageUrl: '<string>',
clearImage: true,
imageAlt: '<string>'
})
};
fetch('https://public.api.paragraph.com/api/v1/posts/slug/{slug}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://public.api.paragraph.com/api/v1/posts/slug/{slug}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'markdown' => '<string>',
'bodyJson' => '<string>',
'title' => '<string>',
'subtitle' => '<string>',
'slug' => '<string>',
'postPreview' => '<string>',
'categories' => [
'<string>'
],
'authorIds' => [
'<string>'
],
'scheduledAt' => 1,
'sendNewsletter' => true,
'canonicalUrl' => '<string>',
'publishOnline' => true,
'communityId' => '<string>',
'publishedAt' => 1,
'imageUrl' => '<string>',
'clearImage' => true,
'imageAlt' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://public.api.paragraph.com/api/v1/posts/slug/{slug}"
payload := strings.NewReader("{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.put("https://public.api.paragraph.com/api/v1/posts/slug/{slug}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://public.api.paragraph.com/api/v1/posts/slug/{slug}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"markdown\": \"<string>\",\n \"bodyJson\": \"<string>\",\n \"title\": \"<string>\",\n \"subtitle\": \"<string>\",\n \"slug\": \"<string>\",\n \"postPreview\": \"<string>\",\n \"categories\": [\n \"<string>\"\n ],\n \"authorIds\": [\n \"<string>\"\n ],\n \"scheduledAt\": 1,\n \"sendNewsletter\": true,\n \"canonicalUrl\": \"<string>\",\n \"publishOnline\": true,\n \"communityId\": \"<string>\",\n \"publishedAt\": 1,\n \"imageUrl\": \"<string>\",\n \"clearImage\": true,\n \"imageAlt\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}{
"success": false,
"msg": "<string>"
}Authorizations
API key for authenticating protected endpoints. Pass as Bearer token in Authorization header.
Path Parameters
URL-friendly identifier of the post to update
1 - 256Body
Body
Post content in Markdown format. Replaces the FULL body. Markdown cannot represent buttons, linked images, or embedded media (videos, tweets, link cards) — replacing a post that has any of those with markdown drops them. Use bodyJson (round-tripped from get-post) to edit an existing post so nothing is lost. Provide markdown OR bodyJson, not both.
Post content as a Tiptap document, JSON-stringified (e.g. '{"type":"doc","content":[...]}'). This accepts ANY Tiptap node the editor supports — including videos, tweets, link cards, callouts, and buttons — so editing a post by round-tripping the json returned by get-post preserves everything markdown would drop. Replaces the FULL body; node-type validity is checked by the renderer and an unusable document is rejected. Provide markdown OR bodyJson, not both.
Title of the post
200Optional subtitle or brief summary
300URL-friendly identifier for the post
1 - 256Preview text for the post
500Category tags for the post. Can also be a comma-separated string.
User ids credited as the post's authors, in byline order. Replaces the full list, so include every author you want kept — read the post's current authorIds first and start from those. Each id must be the publication's owner or an active team member; ids from outside the publication are rejected. There is no endpoint that enumerates members, so an id has to come from a post you have read (authorIds on a single post, or authors[].id on a list) or from the writer.
1 - 20 elementsSet to 'published' to publish a draft or keep an already-live post published after edits, 'draft' to unpublish, or 'archived' to archive
draft, published, archived Unix timestamp (milliseconds) to schedule the post's first publish at a future time. Must be in the future and at most 30 days out. Only valid for draft posts that haven't been published or already scheduled. Cannot be combined with status: 'draft' or 'archived'. Pass null to cancel a previously scheduled publish. The value 0 is treated the same as omitting the field (no scheduling request); note that on an already-scheduled post, omitting scheduledAt while changing status cancels the schedule.
x >= 0Whether to send an email newsletter to subscribers when the post publishes. Only meaningful when publishing (status: 'published') or scheduling (scheduledAt set). Default: false
Canonical URL used in rendered metadata. Pass null to clear it. This does not change the post's Paragraph permalink.
2048Whether the post should be publicly visible online when published. Set false for newsletter-only delivery.
Subscriber segment id for newsletter delivery. Pass null for the general audience. Unavailable or cross-publication segments are rejected rather than broadened.
1 - 128Unix timestamp (milliseconds) to set as the post's publish date. Once set, the date is preserved across re-publishes.
x > 0URL of an image to set as the post's cover/hero image. The image is fetched, re-hosted on Paragraph's CDN, and a placeholder is generated. Pass clearImage: true instead to remove the existing cover.
When true, removes the post's existing cover/hero image. Ignored if imageUrl is also provided.
Alternative text describing the cover image. Set it alongside imageUrl when replacing the image; on its own it re-describes the existing cover. Pass null or an empty string to clear it. Ignored when the post has no cover.
1000Response
Post updated successfully
Whether the update succeeded
true 
