hashnode
Original:🇺🇸 English
Translated
Publish, update and read blog posts on Hashnode via its GraphQL API. Use when the user mentions Hashnode, publishing a blog post to Hashnode, cross-posting an article to their Hashnode blog, saving a Hashnode draft, updating a published Hashnode post, or listing their Hashnode publications and posts.
12installs
Sourceacedatacloud/skills
Added on
NPX Install
npx skill4agent add acedatacloud/skills hashnodeTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Call the Hashnode Public GraphQL API with . The user's Personal
Access Token is in . Single endpoint:
(always a JSON body ).
curl + jq$HASHNODE_TOKENhttps://gql.hashnode.comPOST{query, variables}Every request needs these headers:
Authorization: $HASHNODE_TOKEN # raw token — NO "Bearer " prefix
Content-Type: application/jsonGraphQL always returns HTTP ; real failures live in the JSON
array — always inspect it and show it verbatim. An entry mentioning
/ means the token is invalid → the user must
re-connect the Hashnode connector.
200errorserrorsUnauthorizednot authenticatedHelper — send a query/mutation ( = query string, = variables JSON):
$1$2bash
gql() {
jq -n --arg q "$1" --argjson v "${2:-null}" '{query:$q, variables:$v}' \
| curl -sS -X POST https://gql.hashnode.com \
-H "Authorization: $HASHNODE_TOKEN" \
-H "Content-Type: application/json" \
-d @-
}Always start by confirming the token and finding the publication id
publishPostcreateDraftpublicationIdbash
gql 'query { me { id username publications(first: 10) { edges { node { id title url } } } } }' \
| jq '{me: .data.me.username, publications: [.data.me.publications.edges[].node | {id, title, url}], errors: .errors}'Pick the target blog's (that is the ). If the user has
exactly one publication, use it; otherwise ask which blog to post to.
idpublicationIdPublish a post
Confirm with the user before publishing publicly. If they are not sure, save
a draft first (see below). is required — supply 1–5 tags, each as
(slug lowercase, no spaces).
tags{name, slug}bash
PUB_ID="PUBLICATION_ID" # from the me query above
TITLE="My title"
BODY_MD="$(cat article.md)" # full Markdown body
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
input: {
publicationId: $p,
title: $t,
contentMarkdown: $b,
tags: [{name:"AI", slug:"ai"}, {name:"Web Development", slug:"web-development"}]
}
}')
gql 'mutation PublishPost($input: PublishPostInput!) {
publishPost(input: $input) { post { id slug url title } }
}' "$VARS" | jq '{post: .data.publishPost.post, errors: .errors}'Useful optional fields:
input- — post subtitle.
subtitle - — custom URL slug (otherwise derived from the title).
slug - /
canonicalUrl— when cross-posting, point SEO back to the original source. Set this whenever the same article also lives elsewhere.originalArticleURL - — cover image.
coverImageOptions: { coverImageURL: "https://..." } - — ISO-8601 timestamp to backdate;
publishedAtetc. live underdisableComments.settings
Save a draft (safe, non-public)
Use this when the user has not explicitly approved public publishing:
bash
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
input: { publicationId: $p, title: $t, contentMarkdown: $b }
}')
gql 'mutation CreateDraft($input: CreateDraftInput!) {
createDraft(input: $input) { draft { id slug title } }
}' "$VARS" | jq '{draft: .data.createDraft.draft, errors: .errors}'Update a published post
updatePostidpublishPostbash
VARS=$(jq -n --arg id "POST_ID" --arg b "$(cat article.md)" '{
input: { id: $id, contentMarkdown: $b }
}')
gql 'mutation UpdatePost($input: UpdatePostInput!) {
updatePost(input: $input) { post { id url title } }
}' "$VARS" | jq '{post: .data.updatePost.post, errors: .errors}'List / inspect my posts
bash
gql 'query { me { posts(pageSize: 20, page: 1) { nodes { id title slug url views reactionCount responseCount publishedAt } } } }' \
| jq '[.data.me.posts.nodes[] | {id, title, url, views, reactions: .reactionCount, responses: .responseCount}]'Single post with full content + stats:
bash
gql 'query GetPost($id: ObjectId!) { post(id: $id) { title url views reactionCount responseCount content { markdown } } }' \
"$(jq -n --arg id "POST_ID" '{id:$id}')" | jq '.data.post | {title, url, views, reactions: .reactionCount}'Gotchas
- No prefix — the
Bearerheader carries the raw token.Authorization - is mandatory for publishing/drafting — never guess it, read it from the
publicationIdquery.me - is required on
tags— supply at least onepublishPost; slug must be lowercase with hyphens (e.g.{name, slug}).machine-learning - on HTTP 200 — GraphQL reports failures in the
errorsarray, not via status codes; always surface them.errors - Idempotency — re-running creates a new post each time; to change an existing one use
publishPostwith itsupdatePost.id
Record the output
After you successfully publish and obtain the live result URL, call the built-in
tool ONCE so the user can track this deliverable in My Outputs:
publish_artifactpublish_artifact(kind="article", channel="hashnode", title="<title>", url="<the REAL returned URL>", status="delivered")Use the real returned URL — never fabricate one. Call it once per published item,
only after delivery is confirmed; skip it (or use ) if publishing failed.
See .
status="failed"_shared/artifacts.md