WP Annotation Posts
https://github.com/WordPress/gutenberg/pull/4385
This is an alternative to annotations as a custom comment type in #4386
Create a new annotation.
Note: While the API supports both front and back-end annotations, as a security precaution, front-end annotations are disabled for now. At this time, the main focus is on back-end annotations only.
Request parameters
Parent post password (if protected and authenticated user is unable to edit it, but is allowed to annotate; e.g., edge case for front-end annotations). Defaults to current password cookie.
Request body
Non-annotation post ID.
A front or back-end annotation?
Front-end (default).
Back-end annotation.
Parent ID; i.e., to create a reply.
User ID. Default is that of the authenticated user. Setting this to anything other than the authenticated user’s ID requires edit_others_annotations
.
Non-numeric creator ID/slug; i.e., if created by a specific feature or plugin.
Required only if the creator
field is given.
Display name.
Square image/icon URL.
Post content markup. @TODO: Front-end annotation content should be scrutinized; i.e., treated like a comment instead of like a post in that respect.
W3C annotation selector. Max JSON-encoded size is 16kb. SVG selectors allow up to 128kbs, but those are currently disabled, pending a security review. See also: https://jsfiddle.net/jaswrks/jpcL1t8a/
{
"type": "CssSelector",
"value": "#foo > .bar"
}
{
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
{
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
}
Type of selector.
Can select text nodes!
Pending a security review.
Required for CssSelector type. Other properties apply to other types.
Annotation post status.
Published.
Published (private access only).
Pending future publication.
Draft, unpublished.
Draft revision, unpublished.
Needs review, unpublished.
Revisions inherit parent status.
In the trash.
Annotation substatus. For example, a published annotation can be archived (via substatus) and therefore tucked away, but still visible to others in one way or another. This is possible because the annotation’s main status is still publish
, whereas substatus can be one of the following.
Open (default).
Archived as resolved.
Archived as rejected.
Archived for another reason.
Any meta properties registered using register_meta().
{
"my_key": "my value",
"another_key": {
"another": "value"
}
}
Any value.
Examples
i.e., Also supports all properties associated with post types in the REST API.
{
"parent_post": 1,
"parent_post_target": "admin",
"content": "Hello world!",
"status": "publish",
"via": "gutenberg"
}
{
"parent_post": 1,
"parent_post_target": "admin",
"content": "Hello world!",
"status": "publish",
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo > .bar"
}
}
{
"parent_post": 1,
"parent_post_target": "admin",
"author": 1,
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"via": "gutenberg",
"content": "Hello world!",
"selector": {
"type": "CSSSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
"status": "publish",
"substatus": ""
}
{
"parent_post": 1,
"parent_post_target": "admin",
"author": 1,
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"via": "gutenberg",
"content": "Hello world!",
"selector": {
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
},
"status": "publish",
"substatus": "",
"meta": {
"my_key": "my value",
"another_key": {
"another": "value"
}
}
}
{id}
Get a single annotation by ID.
Path variables
Annotation ID.
Request parameters
REST API context.
Parent post password (if protected and authenticated user is unable to edit it, but is allowed to view). Defaults to current password cookie.
?context=view
{
"id": 1,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/"
},
"modified": "2018-01-11T20:23:33",
"modified_gmt": "2018-01-11T20:23:33",
"slug": "a5a57c7c5a5f0c",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"content": {
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 1,
"parent": 0,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "John Smith",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"substatus": "archive",
"last_substatus_time": 1515741301,
"substatus_history": [
{
"identity": "yoast",
"identity_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"time": 1515741301,
"old": "",
"new": "archive"
}
],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/1"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=1"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
?context=edit
{
"id": 1,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"raw": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/"
},
"modified": "2018-01-12T07:15:01",
"modified_gmt": "2018-01-12T07:15:01",
"password": "",
"slug": "a5a57c7c5a5f0c",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"content": {
"raw": "Hello world!",
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 1,
"parent": 0,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "John Smith",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"substatus": "archive",
"last_substatus_time": 1515741301,
"substatus_history": [
{
"identity": "yoast",
"identity_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"time": 1515741301,
"old": "",
"new": "archive"
}
],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/1"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=1"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
{id}
Update annotation properties.
Path variables
Annotation ID.
Request parameters
Parent post password (if protected and authenticated user is unable to edit it, but is allowed to annotate; e.g., edge case for front-end annotations). Defaults to current password cookie.
Request body
Non-annotation post ID.
A front or back-end annotation?
annotation
Front-end.
Back-end.
Parent ID; i.e., to make it a reply.
User ID. Setting this to anything other than the authenticated user’s ID requires the ability to edit_others_annotations
.
Non-numeric creator ID/slug; i.e., if created by a specific feature or plugin.
Required only if the creator
field is given.
Display name.
Square image/icon URL.
Post content markup. @TODO: Front-end annotation content should be scrutinized; i.e., treated like a comment instead of like a post in that respect.
W3C annotation selector. Max JSON-encoded size is 16kb. SVG selectors allow up to 128kbs, but those are currently disabled, pending a security review. See also: https://jsfiddle.net/jaswrks/jpcL1t8a/
{
"type": "CssSelector",
"value": "#foo > .bar"
}
{
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(3)",
"refinedBy": {
"type": "TextPositionSelector",
"start": 412,
"end": 667
}
}
{
"type": "RangeSelector",
"startSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(2)"
},
"endSelector": {
"type": "CssSelector",
"value": "#foo > ul:nth-child(2) > li:nth-child(8)"
}
}
Type of selector.
Can select text nodes!
Pending a security review.
Required for CssSelector type. Other properties apply to other types.
Annotation post status.
Published.
Published (private access only).
Pending future publication.
Draft, unpublished.
Draft revision, unpublished.
Needs review, unpublished.
Revisions inherit parent status.
In the trash.
Annotation substatus. For example, a published annotation can be archived (via substatus) and therefore tucked away, but still visible to others in one way or another. This is possible because the annotation’s main status is still publish
, whereas substatus can be one of the following.
Open (default).
Archived as resolved.
Archived as rejected.
Archived for another reason.
Any meta properties registered using register_meta().
{
"my_key": "my value",
"another_key": {
"another": "value"
}
}
Any value.
Examples
i.e., Also supports all properties associated with post types in the REST API.
{
"content": "Hello world!"
}
{
"substatus": "resolve"
}
{
"substatus": "reject"
}
{
"substatus": "archive"
}
{
"author": 1,
"parent_post": 1,
"parent_post_target": "admin",
"parent": 0,
"slug": "a5a57c7c5a5f0c",
"status": "publish",
"substatus": "archive",
"content": "<p>Hello world!</p>\n",
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"via": "gutenberg",
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"meta": {
"my_key": "my value",
"another_key": {
"another": "value"
}
}
}
{id}
Delete a single annotation by ID.
Path variables
Annotation ID.
Request parameters
Bypass trash and force deletion?
Trash (default).
Permanently delete.
Parent post password (if protected and the authenticated user is unable to edit it, but is allowed to delete — edge case).
Also supports all the same properties as the REST API for post types; e.g., pagination, order.
Request parameters
REST API context.
Parent post ID. Array or comma-delimited string.
Parent post target. Array or comma-delimited string.
Parent post password; i.e., if protected and the authenticated user is unable to edit it, but is allowed to view. Array or comma-delimited string. In the case of multiple passwords use an array, and the order given must match that of the parent_post
parameter.
Parent annotation ID. Array or comma-delimited string. If hierarchical
is given, parent
defaults to 0
unless defined explicitly. This way hiearchical queries start from top-level annotations by default.
Annotation status. Array or comma-delimited string.
Annotation substatus. Array or comma-delimited string.
Annotation creator. Array or comma-delimited string.
Annotation client identifier. Array or comma-delimited string.
Hierarchical response format? Array or comma-delimited string.
All children (deeply) are simply appended.
Each annotation includes a children
property containing an array of any replies.
?context=view&hierarchical=threaded
[
{
"id": 1,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/"
},
"modified": "2018-01-11T20:23:33",
"modified_gmt": "2018-01-11T20:23:33",
"slug": "a5a57c7c5a5f0c",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"content": {
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 1,
"parent": 0,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "John Smith",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"substatus": "archive",
"last_substatus_time": 1515741301,
"substatus_history": [
{
"identity": "yoast",
"identity_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"time": 1515741301,
"old": "",
"new": "archive"
}
],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/1"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=1"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
},
"children": [
{
"id": 2,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/axtr7c7c5a55cv/"
},
"modified": "2018-01-11T20:23:33",
"modified_gmt": "2018-01-11T20:23:33",
"slug": "axtr7c7c5a55cv",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/axtr7c7c5a55cv/",
"content": {
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 2,
"parent": 1,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "Mary Jane",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": null,
"substatus": "",
"last_substatus_time": 0,
"substatus_history": [],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/2"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/2"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=2"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
},
"children": []
}
]
}
]
?context=edit&hierarchical=flat
[
{
"id": 1,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"raw": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/"
},
"modified": "2018-01-11T20:23:33",
"modified_gmt": "2018-01-11T20:23:33",
"password": "",
"slug": "a5a57c7c5a5f0c",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/a5a57c7c5a5f0c/",
"content": {
"raw": "Hello world!",
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 1,
"parent": 0,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "John Smith",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": {
"type": "CssSelector",
"value": "#foo"
},
"substatus": "archive",
"last_substatus_time": 1515741301,
"substatus_history": [
{
"identity": "yoast",
"identity_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"time": 1515741301,
"old": "",
"new": "archive"
}
],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/1"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/1"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=1"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
},
{
"id": 2,
"date": "2018-01-11T20:23:33",
"date_gmt": "2018-01-11T20:23:33",
"guid": {
"rendered": "https://wp.vm/wp_annotation/axtr7c7c5a55cv/",
"raw": "https://wp.vm/wp_annotation/axtr7c7c5a55cv/"
},
"modified": "2018-01-11T20:23:33",
"modified_gmt": "2018-01-11T20:23:33",
"password": "",
"slug": "axtr7c7c5a55cv",
"status": "publish",
"type": "wp_annotation",
"link": "https://wp.vm/wp_annotation/axtr7c7c5a55cv/",
"content": {
"raw": "Hello world!",
"rendered": "<p>Hello world!</p>\n",
"protected": false
},
"author": 2,
"parent": 1,
"template": "",
"meta": [],
"revisions": null,
"parent_post": 1,
"parent_post_target": "admin",
"via": "gutenberg",
"author_meta": {
"display_name": "Mary Jane",
"image_url": "https://secure.gravatar.com/avatar/e50c12f18247416a1f46f102c5c826c2?s=96&d=mm&r=g"
},
"creator": "yoast",
"creator_meta": {
"display_name": "Yoast",
"image_url": "https://example.com/96x96.png"
},
"selector": null,
"substatus": "",
"last_substatus_time": 0,
"substatus_history": [],
"_links": {
"self": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations/2"
}
],
"collection": [
{
"href": "https://wp.vm/wp-json/wp/v2/annotations"
}
],
"about": [
{
"href": "https://wp.vm/wp-json/wp/v2/types/wp_annotation"
}
],
"author": [
{
"embeddable": true,
"href": "https://wp.vm/wp-json/wp/v2/users/2"
}
],
"wp:attachment": [
{
"href": "https://wp.vm/wp-json/wp/v2/media?parent=2"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
]
Annotation permissions sometimes resemble those associated with comments, but at other times (e.g., back-end annotations) not so much. So these offer a standard method by which to check annotation permissions.
Meta-caps check specific annotations by ID.
current_user_can( 'edit_annotation', 53254 )
read_annotation
edit_annotation
delete_annotation
This custom meta-cap checks a parent post by ID and the would-be parent post target.
current_user_can( 'create_annotation', 123, 'admin' )
create_annotation
General pseudo-caps that optionally support args: parent post ID, target.
current_user_can( 'read_annotations', 123, 'admin' )
current_user_can( 'read_annotations' )
read_annotations
General pseudo-caps that optionally support: parent post target.
current_user_can( 'edit_others_annotations', 'admin' )
current_user_can( 'edit_others_annotations' )
create_annotations
delete_annotations
delete_others_annotations
delete_private_annotations
delete_published_annotations
edit_annotations
edit_others_annotations
edit_private_annotations
edit_published_annotations
publish_annotations
read_private_annotations