Title: WordsPost Content Publisher
Author: WordsPost
Published: <strong>October 28, 2026</strong>
Last modified: October 28, 2026

---

Search plugins

![](https://ps.w.org/wordspost-content-publisher/assets/banner-772x250.png?rev=3771290)

![](https://ps.w.org/wordspost-content-publisher/assets/icon.svg?rev=3771022)

# WordsPost Content Publisher

 By [WordsPost](https://profiles.wordpress.org/wordspost/)

[Download](https://downloads.wordpress.org/plugin/wordspost-content-publisher.3.6.3.zip)

 * [Details](https://wordpress.org/plugins/wordspost-content-publisher/#description)
 * [Reviews](https://wordpress.org/plugins/wordspost-content-publisher/#reviews)
 *  [Installation](https://wordpress.org/plugins/wordspost-content-publisher/#installation)
 * [Development](https://wordpress.org/plugins/wordspost-content-publisher/#developers)

 [Support](https://wordpress.org/support/plugin/wordspost-content-publisher/)

## Description

WordsPost Content Publisher adds a REST API (`content-publisher/v1`) that lets an
external system create, update, delete and search posts, upload and manage media,
manage categories and tags, set SEO fields and, when WPML is active, manage post
languages and translation links. It can also read the rest of the site — pages, 
WooCommerce products, product categories and menus — so the external system can 
see what the site is about and link to it, and list the whole site’s content in 
a light inventory, so the external system can keep its own copy in sync. Every request
is authenticated with an API key. Each client gets its own key — created on the 
plugin’s settings page, or obtained by a service through a confirmation screen —
and every key can be renamed, reissued or revoked on its own.

Everything works without any external service. Optionally, the settings page has
a “Connect to WordsPost” button for the WordsPost content service (https://wordspost.
com): it gives WordsPost its own revocable key after you approve it on a confirmation
screen, instead of copying the key by hand. Nothing is sent to WordsPost unless 
you start the connection and press “Allow” — see External services below.

#### Features

 * Posts: create / read / update / delete / search, with configurable status (including
   scheduled publishing via `date` + `future`), comments, auto excerpt, auto table
   of contents (from H2/H3 headings), mobile-friendly table wrapping with an optional
   card layout on narrow screens, and Cyrillic-to-Latin slug transliteration (Russian
   and Ukrainian)
 * Safe retries: send a `request_key` with `POST /posts` or `POST /media` and a 
   repeated request returns the item created the first time instead of a duplicate,
   even when two copies arrive at once or the first one was interrupted halfway
 * Media: upload by URL or as inline base64 data, with optional WebP conversion,
   automatic downscaling of oversized images, a custom filename, alt/title/caption/
   description, attaching to a post, and lookup by file URL (including generated
   size variants)
 * Categories & tags: full CRUD, with slug transliteration and find-or-create by
   name when attaching to a post; with WPML, per-language terms and translation 
   links
 * Site reading (read only): pages and WooCommerce products with their own fields,
   the visible text of any page — including pages built with Elementor or WPBakery—
   product categories, menus, the site tagline, and whether search engines are told
   not to index each item
 * Site inventory: every post of every public type, every term and every menu, thousands
   per request at a fixed, small database cost — with WPML language and translation
   group, page builder, WPML duplicate flag, former slugs and a content fingerprint
   that changes with the text, SEO fields and categories
 * SEO: one set of fields for Rank Math and Yoast SEO (whichever is active) — title,
   description, focus keyword, canonical, robots, Open Graph, Twitter Card — kept
   with their template variables (`%date%`, `%%title%%`), plus the title and description
   a search engine actually gets after the templates are applied
 * Faithful writes: text is stored exactly as sent, backslashes included, so block
   attributes survive a round trip
 * Repairs: raw texts in the inventory, posts looked up by ID (trash included), 
   WPML duplicates turned into ordinary translations, posts restored from the trash
   into their previous status, terms named by ID and linked into translation groups
 * Safe rewrites: a write that goes through only if the post hasn’t changed since
   it was read and nobody has it open in the editor, writes without pingbacks, per-
   field fingerprints in the inventory, and a version of a post in another language
   made by WPML itself
 * Cache flush: one call clears the cached pages of the given posts (or of the whole
   site) in W3 Total Cache, WP Rocket, LiteSpeed Cache, WP Super Cache or WP Fastest
   Cache, and in WordPress’s object cache
 * Languages: a `/languages` endpoint listing what the site speaks; with WPML, post
   language and translation-group linking via `lang` / `translation_of`, a `translations`
   map on every post, filters to find posts that still need translating, and a clear`
   409` instead of a duplicate when a language in the group is already taken; translations
   keep their original’s slug, change language within their group, and can take 
   the place of a WPML duplicate
 * Keys: a separate API key per client, each revocable on its own, with label, creation
   date and last use; create one by hand on the settings page (shown once, reissue
   it if lost), or let a service obtain one through the “Allow” screen
 * Optional connection to WordsPost: a button on the settings page and an approval
   screen that states exactly what the service will be able to do; disconnect at
   any time
 * Rate limiting: a configurable per-key requests-per-minute safety net against 
   a runaway integration (60 by default), reported in `X-RateLimit-*` headers
 * Activity log: every action the plugin takes, filterable and searchable, kept 
   for 30 days
 * Debug Mode: optional full request/response logging for troubleshooting an integration

The full REST API reference, curl examples and a Postman collection are in the plugin’s`
docs/` folder (`API.md`, `openapi.yaml`, `postman_collection.json`).

### External services

The plugin itself never sends a request to any external service. Two things involve
other sites:

#### Image downloads requested by your API client

When an authenticated API caller passes an address in the `url` field of `POST /
media`, the file at that address is fetched once with WordPress’s own `download_url()`
and stored in the media library. Which addresses are fetched is entirely up to the
site’s own API client; nothing about the site or its visitors is sent. Uploads sent
as inline `data` involve no outgoing request at all.

#### Cache plugins, when your API client asks for a flush

    ```
    POST /cache/flush asks the cache plugin installed on the site to clear its cache, through that plugin's own functions. The plugin sends nothing anywhere itself; if the cache plugin is set up to purge a CDN as well, it does so on its own, as it would when you clear the cache from its screen.
    ```

#### WordsPost (optional, only when you connect it)

WordsPost (https://wordspost.com) is a content service that writes and publishes
posts to connected sites through this plugin’s API. Connecting it is optional and
happens only when a site administrator starts it:

 1. “Connect to WordsPost” on the settings page opens the WordsPost panel (panel.wordspost.
    com) in your browser, with this site’s address in the link.
 2. WordsPost sends your browser back to a confirmation screen on your own site. Only
    after you press “Allow” does your browser return to WordsPost carrying a one-time
    code (valid for 5 minutes), the request’s state value and this site’s address.
 3. WordsPost’s server exchanges that code at this site’s REST API for its own API 
    key. The site answers with the key and basic facts about itself: site name, home
    address, an installation ID, the REST address, the plugin and WordPress versions,
    the rate limit and whether the site is multilingual.

From then on WordsPost uses that key to create, edit and delete posts and media,
as the confirmation screen states. You can revoke it at any time with “Disconnect”
on the settings page. “Cancel” on the confirmation screen sends WordsPost only a
refusal.

WordsPost terms of use: https://wordspost.com/terms/
 WordsPost privacy policy: 
https://wordspost.com/privacy-policy/

## Installation

 1. Upload the plugin folder to `/wp-content/plugins/`, or install the zip through 
    Plugins  Add New  Upload Plugin.
 2. Activate the plugin.
 3. Go to Settings  Content Publisher and choose the user that posts published through
    the API are attributed to.
 4. Under “API keys”, create a key for your integration and copy it — it is shown only
    once. (Or use “Connect to WordsPost” if you use that service.)
 5. In your integration, send the `Authorization: Bearer <key>` header with every request(`
    X-WPPUB-Api-Key: <key>` works the same way).

#### Upgrading from WP Publisher 2.x

Content Publisher is the new name of WP Publisher. WordPress sees it as a separate
plugin, so:

 1. Install and activate Content Publisher next to WP Publisher. On activation it takes
    over WP Publisher’s settings and deactivates WP Publisher.
 2. Delete WP Publisher.
 3. Create a new API key under “API keys” and put it into your integration: since 3.2
    the old site-wide key is not carried over.

The old address `wp-publisher/v1` keeps working in every 3.x version; move integrations
to `content-publisher/v1` when convenient.

## FAQ

### Do I need WordsPost to use this plugin?

No. The plugin is complete on its own: any system that can send HTTP requests (your
own script, an automation tool) can publish through it with an API key. The WordsPost
button is optional and does nothing until you use it.

### Which SEO plugins are supported?

Rank Math and Yoast SEO. If neither is active, requests that include an `seo` field
are rejected with a clear error rather than silently discarding the data.

### Does it require WPML?

No. Without WPML the site is treated as monolingual: `/languages` returns WordPress’s
own locale with `multilingual: false`, and posts report that language. The `lang`
and `translation_of` fields are still accepted but have no effect.

### How is the API secured?

Every request carries an API key in a header. Keys are stored only as SHA-256 hashes,
so the site never keeps them in readable form — which is also why a key is shown
only once. Reissue or revoke any key at any time from the settings page; the old
value stops working immediately.

Everything the API writes is written as the post author chosen in the plugin’s settings,
which must be a user who can write posts but cannot administer the site — an Editor,
for example. Administrators are not accepted, so a key can never change the site’s
settings, users, plugins or themes. The settings page (and the “Connect to WordsPost”
screen) can create such an Editor for you, under a name you choose; until one is
chosen, nothing is published through the API.

A key is a powerful credential: it can publish and delete posts, and read the site’s
content — posts, pages, products and the other public post types, including drafts,
private and password-protected ones, pages a membership plugin hides from visitors
and, by ID, posts in the trash. It can also restore posts from the trash (republishing
what was published), turn WPML duplicates into translations and make WPML versions
of posts in other languages. Give a key only to a service you trust as you would
an editor of the site.

### Does the plugin update itself automatically?

No. It never turns on automatic updates for itself; updates happen only when a site
administrator installs them.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“WordsPost Content Publisher” is open source software. The following people have
contributed to this plugin.

Contributors

 *   [ WordsPost ](https://profiles.wordpress.org/wordspost/)

“WordsPost Content Publisher” has been translated into 2 locales. Thank you to [the translators](https://translate.wordpress.org/projects/wp-plugins/wordspost-content-publisher/contributors)
for their contributions.

[Translate “WordsPost Content Publisher” into your language.](https://translate.wordpress.org/projects/wp-plugins/wordspost-content-publisher)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/wordspost-content-publisher/),
check out the [SVN repository](https://plugins.svn.wordpress.org/wordspost-content-publisher/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/wordspost-content-publisher/)
by [RSS](https://plugins.trac.wordpress.org/log/wordspost-content-publisher/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 3.6.3

 * Translations shipped inside the plugin’s own languages/ folder (as in builds 
   handed out outside WordPress.org) are loaded again. A translation in wp-content/
   languages, such as a language pack from translate.wordpress.org, still takes 
   precedence. No API changes.

#### 3.6.2

 * The API writes only as a post author who cannot administer the site (an Editor,
   for example). Administrators can no longer be chosen; without a suitable author,
   writes are refused with `409 wppub_author_not_allowed` and the dashboard says
   so. The settings page and the “Connect to WordsPost” screen can create such an
   Editor, named as you like (“WordsPost” by default).
 * Translations come from translate.wordpress.org; the plugin no longer bundles 
   or loads its own.

#### 3.6.1

 * The plugin is now called WordsPost Content Publisher (text domain `wordspost-
   content-publisher`). Nothing else changes: settings, keys and the REST API stay
   as they were.

#### 3.6.0

 * `if_hash` on `PUT /posts/{id}` and `POST /posts/{id}/unduplicate`: the write 
   goes through only if the post’s inventory `hash` is still the one given — otherwise`
   409 wppub_changed` with the current one, nothing written. The comparison and 
   the write happen under a row lock, so of concurrent writes with one `if_hash`
   exactly one succeeds.
 * `respect_lock`: `409 wppub_locked` while someone has the post open in the editor.
 * `quiet` on `POST /posts`, `PUT /posts/{id}` and `duplicate-to`: no pingbacks 
   and no enclosure fetching for that write.
 * `field_hashes=1` in the inventory: the MD5 of title, content and excerpt as separate
   fields.
 * `POST /posts/{id}/duplicate-to`: a version of the post in another language made
   by WPML’s own duplication, turned straight into an ordinary translation; the 
   SEO canonical address is not carried over.
 * `unfiltered_html` in `GET /ping` and in the inventory’s `env`: whether texts 
   are stored as sent.
 * `PUT /posts/{id}` with `seo` now writes the SEO fields before saving the post,
   so WPML duplicates get the new title and description along with the new text;
   if the save fails, they go back to what they were (both base paths).
 * A category or tag of a taxonomy WPML doesn’t translate, or one without a WPML
   language, is attached as it is instead of being copied into the post’s language(
   both base paths).
 * A translation whose text is written through the API is marked as edited in WordPress’s
   own editor, so WPML no longer rebuilds it from its original when the original
   is saved (posts with blocks or page-builder content; both base paths).
 * What WordPress and WPML finish at the end of a write request — WPML re-saves 
   translations of posts with blocks there — now runs as the plugin’s author, so
   iframes and scripts in those translations are no longer stripped (both base paths).
 * The featured image, categories and tags of `PUT /posts/{id}` are written before
   the post is saved, so WPML duplicates get them right away; they go back to what
   they were if the save fails (both base paths).
 * All new parameters work under `content-publisher/v1` only; responses without 
   them are as in 3.5.0, apart from `unfiltered_html`.

#### 3.5.0

 * `GET /inventory?kind=texts`: raw title, content and excerpt by id cursor, up 
   to 500 per page and about 8 MB, at a fixed number of database queries per page,
   with the same `hash` as `kind=posts`.
 * `ids=` in `kind=posts` and `kind=texts`: those posts, trashed ones included, 
   and `missing_ids` for the ones that no longer exist.
 * An `env` block in the inventory: WPML, the installation, the post types and taxonomies
   covered and which of them WPML translates.
 * `POST /posts/{id}/unduplicate` turns a WPML duplicate into an ordinary translation
   without changing it; `POST /posts/{id}/restore` brings a post back from the trash
   into its previous status.
 * `categories` and `tags` accept `{"id": N}`; `PUT` of a category or tag accepts`
   translation_of` to link it into a translation group.
 * Moving a post to the trash through the API no longer strips iframes and scripts
   from its content (both base paths).
 * All of this is under `content-publisher/v1` only; responses without the new parameters
   are as in 3.4.0, apart from `env` on the first page of the inventory.

#### 3.4.0

 * New `GET /inventory?kind=posts|terms|menus`: the whole site’s content a page 
   of up to 5000 items at a time, by id cursor, at a fixed number of database queries
   per page — WPML language, translation group and duplicate flag, page builder,
   former slugs, featured image and a `hash` fingerprint for posts; language and
   group for terms and menus; `site_time_gmt` as the “changed since” marker.
 * Posts carry `date_created_gmt` and `date_modified_gmt`.
 * `with_seo_rendered=1` adds the SEO title and description as search engines get
   them (Rank Math, Yoast).
 * Writes keep backslashes (Gutenberg block attributes no longer break) and SEO 
   template variables such as `%date%`; an automatic excerpt leaves shortcodes out.
 * WPML: a translation keeps its original’s slug instead of getting “-2”; `PUT` 
   with `lang` moves a post within its translation group instead of taking it out,
   answers `409` when the language is taken, and `detach: true` takes it out; `replace_duplicate`
   writes a translation into a WPML duplicate; new categories take a `parent`, and
   a second translation into a taken language is a `409`.
 * Saving a term no longer moves a term of another language to the default one, 
   and works while its translation shares its slug.
 * New `POST /cache/flush`.
 * Posts carry `source_lang` (the language WPML records them as translated from)
   and `translation_jobs` (open WPML translation jobs in their group); `GET /languages`
   adds `wpml_auto_translate` (WPML’s automatic translation mode and post types),
   so an integration can stay out of what WPML translates by itself.
 * All of this is under `content-publisher/v1` only (the term fix applies everywhere);
   without the new parameters responses are the same as in 3.3.1, apart from the
   two new dates.

#### 3.3.1

 * `noindex` now reflects only the item itself — its SEO settings, its post type’s
   default, a status other than published, or being a WooCommerce cart/checkout/
   account page — and no longer the site-wide “Discourage search engines” setting,
   which made every item of a site under development look the same. That setting
   is reported on its own as `search_engines_discouraged` in `GET /ping`.

#### 3.3.0

 * Read pages and WooCommerce products: `GET /posts?type=page|product` and `GET /
   posts/{id}?type=…`, with page fields (parent, order, template, front page, posts
   page) and product fields (categories, SKU, stock, catalog visibility). Writing
   is still for blog posts only.
 * `with_text=1` adds `text`: the content as a visitor reads it — rendered like 
   the site’s front end (blocks, shortcodes, Elementor, WPBakery), then reduced 
   to plain text of up to 3000 characters. Embeds are not fetched while doing so.
 * `modified_after` returns only what changed since a given moment.
 * With an explicit `type`, password-protected items are left out, `seo.robots` 
   shows the robots rules that actually apply (including Rank Math’s and Yoast’s
   post type defaults), and a new `noindex` flag sums them up.
 * New `GET /terms?taxonomy=category|post_tag|product_cat` with each term’s link,
   description and parent, and `hide_empty`.
 * New `GET /menus`: menus in theme locations and unassigned ones, per WPML language.
 * `GET /ping` adds `tagline`, `front_page_id`, `posts_page_id` and `woocommerce`,
   and five new `features`.
 * All of this is under `content-publisher/v1` only; without the new parameters 
   every response is the same as in 3.2.

#### 3.2.2

 * A “Settings” link in the plugin’s row on the Plugins screen.

#### 3.2.1

 * When a key is revoked (on the settings page or through `DELETE /keys/self`) while
   its value is still shown in the “new key” box, the box disappears, so a dead 
   key can’t be copied by mistake. A reissue also removes the old value from other
   administrators’ boxes.

#### 3.2.0

 * The site-wide primary API key is gone. Every key now belongs to one client: created
   by hand under “API keys” or obtained through “Connect to WordsPost”. Updating
   removes the old primary key; anything still using it must switch to a key of 
   its own.
 * A newly created or reissued key is shown in its own box with a Copy button and
   stays there (for up to 10 minutes) until you press “I have copied it” — it no
   longer disappears on a second page load or when another plugin hides admin notices.
 * Keys created by hand can be reissued: same label, new value, the old value stops
   working. Service keys can only be revoked.
 * `DELETE /keys/self` works for every key (the `wppub_primary_key` error no longer
   exists). The `primary` field of a key description is always `false`.
 * The “Test Connection” button is removed (it used the primary key). Resetting 
   settings no longer touches API keys.

#### 3.1.1

 * Settings page: API keys are now one table. The primary key is its first row, 
   with icons to show, copy and regenerate it; every other key has icons to rename
   and revoke it. A service’s key can’t be regenerated from here (the service would
   never learn the new key) — revoke it and connect again instead.
 * “Connect to WordsPost” moved to a compact line above the keys table; once connected,
   the service shows up as a row of the table.
 * No API changes.

#### 3.1.0

 * Named API keys: a separate, individually revocable key per client, stored only
   as a hash; a “Keys” section on the settings page to create (shown once) and revoke
   them. The primary key works exactly as before.
 * Optional “Connect to WordsPost” button and approval screen (authorization code
   with PKCE, modelled on core’s Application Passwords flow). The plugin makes no
   outgoing request; the key is handed over only after “Allow”.
 * New endpoints: `GET /connect/info` and `POST /connect/exchange` (public), `GET/
   keys/self` and `DELETE /keys/self`.
 * Rate limit: 60 requests per minute by default (was 20; a value you saved is kept),
   counted atomically, refused requests no longer count, and `X-RateLimit-Limit`,`
   X-RateLimit-Remaining`, `X-RateLimit-Reset` headers plus `Retry-After` on 429.
 * `GET /ping` also reports `rate_limit`, `key`, `home_url`, `install_id` and `rest_root`;
   new features `keys`, `connect`, `rate_headers`.
 * Deleting the plugin now removes its settings, keys and logs (uninstall).

#### 3.0.0

 * Renamed to Content Publisher (slug `content-publisher`). New REST address `content-
   publisher/v1`; the 2.x address `wp-publisher/v1` keeps working throughout 3.x.
 * Activating 3.0.0 takes over the API key and settings of WP Publisher 2.x and 
   deactivates it — the key stays the same.
 * New `request_key` on `POST /posts` and `POST /media`: a repeated request returns
   the existing item with `200` and the `X-WPPUB-Replayed: 1` header instead of 
   creating a duplicate. Concurrent requests with one key are serialized; a retry
   after an interrupted request finishes the missing steps without a second insert.
   Lookup via `GET /posts?request_key=` and `GET /media?request_key=`.
 * `POST /posts` with `translation_of` now answers `409 wppub_translation_exists`(
   with the existing post’s `id` and `link`) when the translation group already 
   has a post in that language, instead of creating a second post outside the group.
 * `PUT /media/{id}` accepts `post_id` to attach an attachment to a post (`0`/`null`
   detaches it).
 * `GET /ping` reports `features`, so a caller can tell what the site’s plugin version
   supports.
 * Ukrainian translation added.

#### 2.0.0

 * Major version: stable `wppub_` prefix for every option, error code, and internal
   hook, and a stable `/wp-publisher/v1` REST namespace. `Authorization: Bearer 
   <key>` is now the primary way to authenticate (`X-WPPUB-Api-Key` remains available
   as an equivalent alternative).
 * This is a new plugin install from WordPress’s point of view (different folder/
   slug) — see Upgrade Notice.

#### 1.7.0

 * `POST /media` now accepts a `data` field — base64-encoded file bytes — as an 
   alternative to `url`, for images that only exist as bytes with no public link
   to download from. Mutually exclusive with `url`. The real file type is read from
   the file’s own content, never trusted from a claimed `mime_type`; only JPEG, 
   PNG, and WEBP are accepted this way, with a 10MB limit.
 * New `filename` field (works with both `url` and `data`) actually takes effect
   now — previously it was accepted but silently ignored, so uploads kept the source
   URL’s filename regardless. The extension is always the real one (from file content),
   never whatever the caller or the source URL happened to say.
 * New `GET /media?url=<file URL>` — resolves an already-uploaded attachment’s ID
   from its file URL, for adding metadata to images already sitting in published
   content. Understands the size-variant URLs WordPress itself generates (`photo-
   1024x683.jpg`), and accepts a site-relative path as well as a full URL.
 * Error code `wppub_missing_url` on `POST /media` (when neither `url` nor `data`
   is given) is now `wppub_missing_source`, to describe both required-field cases
   correctly.

#### 1.6.0

 * New `wrap_tables` field on `POST`/`PUT /posts`: wraps every `<table>` in the 
   post content in a `.wm-table-scroll` region (keyboard-focusable, `role="region"`,`
   aria-label` in the article’s own language) so wide tables scroll horizontally
   on narrow screens instead of squeezing their text unreadably thin — the actual
   cause of “tables look broken on mobile,” not overflow. `true` wraps (or re-wraps,
   idempotently), `false` removes the wrapper again; left absent on `POST` it follows
   the new “Wrap tables by default” setting, and on `PUT` it leaves existing markup
   untouched unless given explicitly.
 * Narrow tables (2 up to a configurable column count, default 4, with an unambiguous
   single-row header) additionally get a card layout below 600px: each row becomes
   its own block, the first column as a bold heading and the rest as “label: value”
   lines built from `data-label` attributes set at publish time. Wider tables, or
   ones without a clean header row, still get the scroll wrapper but keep their 
   normal row/column layout at every width.
 * New front-end stylesheet (`public/assets/tables.css`), enqueued ahead of the 
   active theme’s own CSS so a child theme can override any of it without `!important`;
   includes a right-edge fade that appears only while there’s more table to scroll
   to, and disappears once fully scrolled.
 * Two new settings: the table-cards column threshold, and the default for `wrap_tables`
   on `POST` when the field is omitted.

#### 1.5.0

 * Lowered the minimum required PHP version from 8.0 to 7.4, for wider hosting compatibility.
   No code change was needed — the plugin never used any PHP 8.0+ syntax; the 8.0
   floor was just how it was first declared. Confirmed with a static PHPCompatibility
   scan targeting 7.4 (zero issues) in addition to a manual review.

#### 1.4.0

 * Categories and tags now carry the same WPML language support posts got in 1.3.0:`
   POST /categories`/`/tags` accept `lang` and `translation_of`; every term response
   gains `lang` and a `translations` map (`{language code: {id, name, slug}}`, including
   the term itself, `{}` without WPML); `GET /categories`/`/tags` gain `lang` (a
   code, or `all`) and `untranslated=<code>` query filters, matching the ones `GET/
   posts` already had.
 * The important part: attaching `categories`/`tags` by name on `POST`/`PUT /posts`
   now resolves the name to the term’s version in the post’s own language — not 
   just whichever language that name happens to already exist in. A term found in
   the “wrong” language gets its translation looked up (and reused, not duplicated,
   on every later post) or, if none exists yet, a new term under the same name is
   created and linked via `translation_of`. This means the same `categories`/`tags`
   list can be sent for an article and its translation without knowing each term’s
   ID or name in every language.

#### 1.3.0

 * New `translations` field on every post — a map of language code to `{id, status}`
   across the post’s whole WPML translation group, including the post itself. Includes
   translations in any status (a draft-in-progress counts as “translated” for this
   purpose), so a workflow scanning for missing translations doesn’t duplicate one
   that’s already started. Empty object without WPML.
 * New `lang` query parameter on `GET /posts` — a language code or `all`. Without
   it, WPML filters results to the site’s default language only; this was previously
   implicit and undocumented.
 * New `untranslated=<language code>` query parameter on `GET /posts` — returns 
   only posts whose translation group has no version (any status) in that language,
   so a workflow doesn’t have to page through the whole site filtering client-side.
 * Fixed three bugs in 1.2.0’s scheduled publishing: `status: future` with a past`
   date` no longer silently published immediately instead of returning `400 wppub_missing_date`;`
   PUT` with only `date` (no `status`) on an already-published post now correctly
   schedules it when the new date is in the future (an undefined-variable bug meant
   it never did); and `PUT` with `date` on a draft/pending post that never had an
   explicit date of its own no longer silently discards the new date — WordPress
   does that by default unless told otherwise, which the plugin now does. Also, 
   a `date` made only of whitespace used to silently mean “right now”; it’s now 
   treated as not provided, same as an empty string.

#### 1.2.0

 * New `date` field on `POST`/`PUT /posts` — publish date/time, read in the site’s
   timezone when the value carries no offset of its own, written to both `post_date`
   and `post_date_gmt`.
 * New `future` status. You don’t normally need to set it directly: send `status:
   publish` together with a future `date` and the post is scheduled automatically;
   WordPress publishes it at that time.
 * Creating a post with `status: future` requires a `date` that is actually in the
   future — a missing or non-future date returns `400 wppub_missing_date`, an unparseable
   one returns `400 wppub_invalid_date`. (Updating a post with `status: future` 
   and no `date` keeps its existing stored date instead of erroring — see API.md.)
 * One minute of slack: a `date` at “now” publishes immediately rather than scheduling.

#### 1.1.0

 * `/languages` now works on sites without WPML: it returns WordPress’s own locale
   as a single language plus a `multilingual` flag, instead of an empty list.
 * `lang` in post responses is the site language on non-WPML sites instead of `null`.
 * `lang` / `translation_of` no longer return 400 without WPML — they are accepted
   and inert, so one integration fits both single- and multi-language sites.
 * Table of contents heading is now written in the post’s language, not the site’s
   admin language (built-in translations for ru, uk, en, de, fr, es, pl; `wppub_toc_title`
   filter to add more).
 * New `toc_title` field to override that heading per request.
 * `generate_toc` in PUT /posts/{id} now works without `content`: true adds or rebuilds
   the table of contents in the existing post, false removes it. Regeneration replaces
   the old block instead of stacking a second one.
 * Table of contents markup is now `<nav class="wppub-toc">` with a class on every
   element (`__title`, `__list`, `__list--sub`, `__item`, `__item--sub`, `__link`)
   so it can be styled to match the theme. Blocks written by 1.0.0 are recognised
   and replaced on the next regeneration.

#### 1.0.0

 * First stable release.
 * REST API for posts, media, categories, tags.
 * Rank Math / Yoast SEO field support.
 * WPML language and translation-group support.
 * Rate limiting and automatic image downscaling, both configurable.
 * Activity log with filtering, search, and automatic cleanup.
 * Debug Mode for full request/response logging.
 * Reset Settings action (author, Debug Mode, rate limit, max image dimension back
   to defaults, new API key).
 * Russian (ru_RU) translation included.

## Meta

 *  Version **3.6.3**
 *  Last updated **3 days ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.1.4**
 *  PHP version ** 7.4 or higher **
 *  Languages
 * [English (US)](https://wordpress.org/plugins/wordspost-content-publisher/), [Russian](https://ru.wordpress.org/plugins/wordspost-content-publisher/),
   and [Ukrainian](https://uk.wordpress.org/plugins/wordspost-content-publisher/).
 *  [Translate into your language](https://translate.wordpress.org/projects/wp-plugins/wordspost-content-publisher)
 * Tags
 * [automation](https://wordpress.org/plugins/tags/automation/)[media](https://wordpress.org/plugins/tags/media/)
   [publishing](https://wordpress.org/plugins/tags/publishing/)[rest-api](https://wordpress.org/plugins/tags/rest-api/)
   [wpml](https://wordpress.org/plugins/tags/wpml/)
 *  [Advanced View](https://wordpress.org/plugins/wordspost-content-publisher/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/wordspost-content-publisher/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/wordspost-content-publisher/reviews/)

## Contributors

 *   [ WordsPost ](https://profiles.wordpress.org/wordspost/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/wordspost-content-publisher/)