WordPress

Already have a WordPress site? Point your blog at it and every post you publish here is pushed there automatically — as a real WordPress post, in your theme, at your permalinks. No plugin to install. WordPress core has shipped everything needed since 5.6: the REST API and Application Passwords.

Connect in three steps

  1. Create an application password in WordPress

    In WordPress, go to Users → Profile, scroll to Application Passwords, type a name (baas), and press Add New Application Password. WordPress shows a 24-character password once — copy it now.

    Connect as an administrator or editor. Those roles hold the unfiltered_html capability, which is what lets your rich content arrive intact — see What survives the push below.

  2. Paste it into the blog's delivery card

    On your blog page in baas, find Where posts go, switch delivery to Push to WordPress, and fill in:

    Field Value
    WordPress site URL Your site root, e.g. https://example.com — not the /wp-admin URL
    WordPress username The login name of that user, not their email
    Application password The 24-character string, spaces and all
    Posts arrive as Published, or Draft if you want a final look inside WordPress

    We store the password encrypted (AES-256-GCM) and never display it again — the field stays blank on every later visit, and saving with it blank keeps the stored one.

  3. Test the connection

    Press Test connection. A green result names the WordPress user we authenticated as. Anything else tells you exactly what WordPress said — see Troubleshooting below.

What gets pushed, and when

A post is pushed when it becomes published — the moment you approve it in the review queue, or the moment auto-publish publishes it for you. The push carries:

WordPress fieldFrom
titleThe post title
slugThe post slug — your permalink stays predictable
contentThe rendered HTML, wrapped and styled (below)
excerptThe generated excerpt
statusWhatever you chose in Posts arrive as
date_gmtThe publish time recorded here

Pushing again updates the same WordPress post rather than creating a second one: we remember the WordPress post id. If you delete the post inside WordPress, the next push recreates it.

Taking a post down

Take down on a post pulls it here and on WordPress: the article stops being served by us, and its WordPress copy is set back to draft, so it answers 404 on your site while the content stays in your CMS. We never delete or trash a post on your site — the draft keeps the same WordPress post id, so Publish again pushes over the same post instead of creating a second one.

If WordPress cannot be reached, the withdrawal retries with backoff and the card shows the error; the post is already down here either way.

What survives the push

Our posts are richer than plain HTML — callouts, step guides, comparison panels, charts, annotated code. WordPress decides how much of that it accepts based on one capability of the user you connected as: unfiltered_html.

Which user did you connect as?
Administrator or editor

Holds unfiltered_html on a single-site install.

  • Full component styling — we send a scoped <style> block
  • Charts, flow diagrams, sparklines (SVG) all render
  • Your blog theme palette travels with the content
  • The styles are scoped to our wrapper and cannot repaint your theme
Author, contributor, or any multisite user

Lacks unfiltered_html, so WordPress runs your content through its KSES filter.

  • We deliberately send no <style> block — WordPress strips the tags but leaves the CSS behind as visible text, which would dump a stylesheet into your article
  • SVG is removed entirely, so charts and diagrams disappear
  • Text, headings, lists, tables, code, callout structure still arrive

The dashboard warns you about this after a connection test, so you find out before your readers do. On WordPress multisite, only a Super Admin holds unfiltered_html — that is a WordPress rule, not ours.

SEO fields (Yoast and Rank Math)

We send the focus keyphrase, SEO title, and meta description for both Yoast SEO and Rank Math on every push. Whether they stick is up to your site:

To make them stick, drop this must-use plugin into wp-content/mu-plugins/baas-seo-bridge.php — byte-identical to the file this project's own compose stack mounts into its WordPress dogfood target:

<?php
/**
 * Plugin Name: baas SEO bridge
 * Description: Exposes the Yoast SEO and Rank Math post-meta fields over the
 *              WordPress REST API so an external publisher can set the focus
 *              keyphrase, SEO title, and meta description when it pushes a
 *              post. Without this, WordPress silently drops those keys. Also
 *              registers the _baas_post_id ownership marker so a retried push
 *              recognises a post it already created and updates it in place
 *              instead of publishing a duplicate — and never touches a post it
 *              did not create.
 * Version:     1.1.0
 * License:     MIT
 *
 * Drop this file in wp-content/mu-plugins/ — must-use plugins load
 * automatically and cannot be deactivated by accident.
 */

defined('ABSPATH') || exit;

add_action('init', function () {
    $keys = [
        // Yoast SEO. Leading underscore = protected meta, which the REST API
        // refuses to touch unless it is registered with an auth_callback.
        '_yoast_wpseo_focuskw',
        '_yoast_wpseo_metadesc',
        '_yoast_wpseo_title',
        // Rank Math. Not underscore-prefixed, but still unregistered for REST
        // on a stock install, so it needs the same treatment.
        'rank_math_focus_keyword',
        'rank_math_description',
        'rank_math_title',
        // baas ownership marker: our own post uuid, written on create and read
        // back (context=edit) so an id-less retry adopts only the post WE made.
        '_baas_post_id',
    ];

    foreach ($keys as $key) {
        register_post_meta('post', $key, [
            'type'          => 'string',
            'single'        => true,
            'show_in_rest'  => true,
            'auth_callback' => function ($allowed, $meta_key, $post_id) {
                // Same gate the editor screen uses: whoever may edit the post
                // may set its SEO fields.
                return current_user_can('edit_post', $post_id);
            },
        ]);
    }
});

// Keep the ownership marker out of PUBLIC (view-context) REST responses. We
// only ever read it back in edit context (authenticated) to recognise a post
// we created; exposing it to anonymous callers would leak our internal post
// id and baas provenance on the customer's own public site.
add_filter('rest_prepare_post', function ($response, $post, $request) {
    if (($request['context'] ?? 'view') !== 'edit') {
        $data = $response->get_data();
        if (isset($data['meta']['_baas_post_id'])) {
            unset($data['meta']['_baas_post_id']);
            $response->set_data($data);
        }
    }
    return $response;
}, 10, 3);

Everything else — title, slug, content, excerpt, publish date, status — works on stock WordPress with no plugin at all.

Pull with the plugin instead

If you would rather not hand us an application password, install the DailySmith Sync plugin and let WordPress pull. It polls the sync feed hourly with your site's public key, creates or updates posts with the same content, SEO fields, featured image and styling the push sends, and never overwrites a post it did not create. It also registers the SEO fields and the ownership marker above, so with it installed you do not need the bridge snippet, and a site running both channels ends up with one WordPress post per article.

The plugin is open source (MIT) and lives on GitHub: TechnicalInsideTI/dailysmith-wordpress-plugin. Download the zip from the latest release, upload it under Plugins → Add New, then open Settings → DailySmith Sync and paste your pk_… key.

Troubleshooting

What you seeWhat it means
401 (incorrect_password)The application password is wrong or was revoked. Generate a new one — the old value is unrecoverable.
401 with a correct passwordYour server is stripping the Authorization header. On Apache, add RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}] right after RewriteEngine On in .htaccess.
403 (rest_cannot_create)The user authenticated but is not allowed to create posts. Use an editor or administrator.
404 on the REST routePretty permalinks are off, or a security plugin has disabled the REST API. Set Settings → Permalinks to anything other than "Plain".
"returned something that is not JSON"The URL is not the site root — use https://example.com, not https://example.com/wp-admin.
"Application passwords are not available"WordPress only offers them over HTTPS. Put the site behind TLS.
Styling missing on WordPressThe connected user lacks unfiltered_html — see What survives the push above.

Security notes

  • The application password is stored encrypted at rest (AES-256-GCM, with a fresh nonce per value and the ciphertext bound to the blog it belongs to). It is never returned by any API response, never logged, and never rendered in the dashboard.
  • An application password is not your WordPress login. Revoking it in Users → Profile cuts us off immediately and changes nothing else.
  • Give it the least role that can publish. Editor is usually right; administrator is only needed if you also want plugin-level SEO fields set by a user who can install the bridge snippet.
  • We require https:// for the site URL. An application password sent over plain HTTP is readable by anything on the path.

Self-hosting note

Operators running their own baas deployment must set BAAS_ENCRYPTION_KEY to 32 random bytes, base64 encoded (openssl rand -base64 32); without it the API refuses to store WordPress credentials at all. To rotate: move the current value into BAAS_ENCRYPTION_KEYS_OLD (comma-separated, decrypt-only), put a fresh key in BAAS_ENCRYPTION_KEY, and restart. Stored credentials keep working because the retired key still decrypts them — but they are not re-encrypted automatically. To finish retiring the old key, re-save each WordPress connection (that rewrites it under the new key), then drop the retired key from BAAS_ENCRYPTION_KEYS_OLD.

enro