# Assign sites
Source: https://docs.wpos.ai/agencies/assign-sites
Decide which client sites each member works on, and give each of them their own WordPress login on those sites.
Members only see the sites you assign them. Assigning a site does two things
at once: it puts the site in the member's site list, and it gives them a
WordPress login of their own on that site. Every person's work on a client
site then runs under their own WordPress account, not a shared one.
## Who sees what
* **The owner** sees every site and sandbox in the workspace.
* **Agency Managers and Operators** see the sites assigned to them, plus any
sandboxes they created themselves.
Until something is assigned, a new member's dashboard says **No sites assigned
yet** and tells them who decides which sites they work on. The owner assigns
sites to anyone; an Agency Manager assigns sites to their own operators, and
only sites the manager has been assigned themselves.
## Assign sites to a member
1. On the [Members](/agencies/members) page, click the **Assign sites** icon on
the member's row.
2. The **Sites for** page lists every site you can assign, with sandboxes
tagged **Sandbox**. A search box appears when the list is long.
3. Click **Assign site** next to each site they should work on, and choose
their WordPress login (see below). Click **Unassign** to take a site away.
Changes apply immediately; there is no Save button.
## Assign a site to people
To work from the site's side instead, open the site's three-dot menu on the
dashboard and choose **Assign site**. The page lists everyone in the workspace
who can be put on that site, with **Everyone**, **Managers**, and
**Operators** tabs and a search box. People already on the site are marked
**On this site**. Use **Assign site** and **Unassign** the same way.
## Choose their WordPress login
Assigning a site needs a WordPress login to go with it. When you click
**Assign site** and the member has no login on that site yet, the **WordPress
users** panel opens and asks you to select one. You can give them an account
that already exists, or create a new one.
Tick the account in the list. A dialog asks what to do about its
password:
* **Keep the current one**, when the member already holds this account.
* **Set a new one**, to give the account a new password.
* **I already know it**, when you will pass on the password yourself.
Click **Assign login**.
Click **Create new user**. The **Create an account for** dialog asks for a
**Username** and **Email** (filled in with the member's address), an
optional first and last name, a **Password** (type one or click
**Generate**), and a **Role**.
**Give this account to** the member and **Email the new user about their
account** are both ticked by default. Click **Add user**.
Roles offered are Subscriber (the default), Contributor, Author, Editor,
and Administrator. Only the workspace owner can create an Administrator.
Once the login is chosen, the site is assigned and a confirmation names the
site and the member. Each member holds one WordPress account per site, and
each account belongs to one member at a time.
Creating WordPress accounts needs the **Create WordPress users** permission,
which only the owner has by default and which cannot be given to operators.
Giving someone an account that already exists does not need it.
## Manage the WordPress users on a site
The **WordPress users** panel lists the site's accounts, ten to a page, and
shows who holds each one. From it you can:
* **Set a password.** The new password is shown once, with a copy button.
Only the owner can set the password on a WordPress Administrator.
* **Release an account.** **Release account** takes the login back from the
member. If WPOS set its password, the password is changed as part of the
release. If you entered it under **I already know it**, it is left as it
is.
* **Remove a user.** Choose whether their content is deleted with them or
moved to another user. WordPress does not allow the primary administrator to
be removed, and an account someone holds has to be released first.
## Unassigning a site
**Unassign** takes the site off the member and frees their WordPress account
so it can be given to someone else.
* If WPOS set the account's password, it is changed, so the old one no longer
signs in.
* If WPOS did not set it, it is not changed. They can still sign in until you
change it in wp-admin.
* If the site cannot be reached, WPOS says the password could not be changed.
Reset it on the site to lock them out.
Disabling a member unassigns all of their sites. See
[Members](/agencies/members).
## What the member sees
A member's site list shows only their assigned sites and their own sandboxes.
The key icon on a site's row, **Show login credential**, opens their WordPress
login for that site: the login URL, their username, and their password behind
**Reveal password**, each with a copy button.
### Member license keys
In **Settings**, a member sees **Your license key** instead of the account's
key. It works only on the sites they are assigned ("Works on the N sites you
are assigned"), and credits spent with it come out of their own budget. Before
anything is assigned, it says the key will start working once they get a site.
Roles, teams, credit budgets, and managing each member.
Bring agency managers and operators into the workspace.
# Billing
Source: https://docs.wpos.ai/agencies/billing
Plans, credits, and site licenses, and how they work together in WPOS.
Billing in WPOS is built around three things: a **plan**, a pool of **credits**,
and a set of **site licenses**. This page explains how they fit together. For
current plan names and prices, the source of truth is the pricing page on
[wpos.ai](https://wpos.ai) and your account at [app.wpos.ai](https://app.wpos.ai).
This page describes how billing works in general. Exact names, limits, and
prices live on the pricing page and in your account, which always win over
anything written here.
## The three pieces
Your subscription. Sets your monthly credits and how many sites you can
operate before adding more.
The unit of work. Running the agent spends credits. Your plan refills them
each month.
Each connected site uses one license. Plans include a number of them, and you
can add more as your fleet grows.
## How credits work
Credits are the unit WPOS bills work in. When the agent runs work on a site, it
spends credits from your account balance.
* Your plan tops your balance up to its monthly allotment on each billing cycle.
* Usage is tracked per account, so you can see where credits go across every site
you operate.
* Heavier work spends more than light work. Small, scoped requests in the
[Command Center](/operate/command-center) cost less than large open-ended ones.
### Seeing your balance and usage
Your current credit balance shows in the top corner of the
[Command Center](/operate/command-center) at all times. For a fuller breakdown,
open the **Usage** tab in [Settings](/operate/settings) to see how credits are
being spent on that site.
Need more than your monthly allotment in a busy month? Credit packs are one-time
top-ups that do not expire, so a heavy month does not force a permanent plan
change. See the pricing page for pack sizes.
## Site licenses
Every site you connect consumes one site license. Your plan includes a set number,
and you add more when you connect more sites. Members on a site are not billed per
seat, so bringing your team onto a site does not change the cost.
## Managing billing
Go to [app.wpos.ai](https://app.wpos.ai) and open billing.
See your current credit balance and where it is going across connected sites.
Change plan, add site licenses, or buy a credit pack as your fleet and
workload grow.
Running many sites and want a plan built for that? See agency pricing on
[wpos.ai](https://wpos.ai), or contact
[support@wpos.ai](mailto:support@wpos.ai).
# Client handoff
Source: https://docs.wpos.ai/agencies/handoff
Hand a finished site to the client with WPOS locked behind your agency's brand, and keep operating it yourself.
Handoff mode is how an agency delivers a site without handing over the keys
to the AI. With handoff on, clients working in wp-admin see your agency's
name and logo instead of an open Command Center, while you keep full access
and keep operating the site.
Configure it per site in [Settings](/operate/settings), on the **Handoff**
tab.
## What you control
* **Handoff Mode.** The master switch. Off while you build; on when the
client takes the site day to day.
* **Agency name and logo.** Shown to clients while handoff is active, so
the delivered site carries your brand, not WPOS's.
* **Show widgets.** Clients can view the AI-built widgets read-only. On by
default.
* **Show snippets.** Clients can view and browse code snippets. On by
default.
* **Show chat.** Leaves the AI chat visible to clients. **Off by default**:
most agencies keep the agent to themselves and sell the outcomes.
* **Password.** The handoff password is what unlocks the full Command Center
on that site. Keep it with the team; a client with the password has full
access.
## How you keep working
Handoff locks the surface, not you. Unlock the Command Center on the site
with the handoff password when there is work to do, and lock it again when
you are done. You can also switch handoff on or off per site remotely from
the [Workspace](/agencies/workspace) at
[app.wpos.ai](https://app.wpos.ai), with no password entry at the site.
Turn handoff on as part of your delivery checklist, in the same pass where
you review [billing](/agencies/billing) for the site. Delivered sites
should never show an open chat to a curious client.
# Invite members
Source: https://docs.wpos.ai/agencies/invite-members
Add agency managers and operators to your workspace by email or with a one-time invite link, and choose what they may do before they join.
Invite people from the [Members](/agencies/members) page at
[app.wpos.ai](https://app.wpos.ai). You can email an invite to one address, or
create a link to share with the person yourself. Either way, you choose their
role and what they may do before they join.
Owners can invite **Agency Managers** and **Operators**. Agency Managers can
invite **Operators** to their own team.
## Open the invite screen
On the Members page, click **Add member**. The **Invite a member** screen has
two tabs: **Invite by email** and **Invite by link**.
## Choose the role and what they may do
Both tabs start with the same two choices.
* **Role.** The owner picks **Agency Manager** or **Operator** (Operator is the
default). A manager can only invite operators, so the role is fixed for
them.
* **Permissions.** The strip shows how many switches are on, for example
**13 of 19 allowed**. Open it to see **What an Operator may do** (or **What
an Agency Manager may do**) and switch individual permissions on or off.
Filter the list by area: Sandbox, Site, Handoff, Plan, Connector, Support,
or Referral. See [Permissions](/agencies/permissions) for what each switch
allows.
The switches start on for everything you can pass on, except a few powerful
ones that always start off: **Create sandboxes**, **Delete sandboxes**,
**Delete sites**, **Cancel the plan**, **Create WordPress users**,
**Disconnect connectors**, **View referrals**, and **Share the referral
link**. Turn those on only for someone who needs them.
Operators are never offered the billing permissions or **Create WordPress
users**.
An Agency Manager can only pass on permissions they hold themselves. The
exceptions are **Add sites** and **Create sandboxes**, which a manager can
give even without holding them.
## Invite by email
On **Invite by email**, type their address in **Email**, set the role and
permissions, and click **Send invite**.
Before anything is sent, WPOS stops invites that cannot work: your own
address, someone who is already a member, or an address that already has
an invite waiting. To send a fresh invite to that address, remove the
waiting one under **Pending invites** first.
The invite is for that email address only and expires after 7 days. The
confirmation on screen tells you how they will get in.
How they get in depends on whether they already use WPOS:
* **New to WPOS.** WPOS creates their account, and the email carries a
temporary password. They sign in and join the workspace straight away.
* **Already have a WPOS account.** They sign in with their existing account
and accept the invite from the email.
* **Sign in with Google.** They join with **Continue with Google**.
If the email cannot be delivered, the screen shows the invite link instead so
you can share it yourself.
## Invite by link
A link is useful when you do not know the person's address yet, or want to
send the invite through chat.
1. On **Invite by link**, set the role and permissions.
2. Click **Create invite link**, then copy the link and share it.
Each link works once: the first person who joins with it uses it up. A link
expires after 3 days. There is one live link per role, so if a link already
exists, **Show link** brings it back instead of making a new one.
The permissions are fixed when the link is created. To change them, or to
close a link you shared by mistake, click **Reset link**. The old link stops
working immediately and a new one replaces it.
When someone opens the link while signed out, they click **Continue with
email**, enter their work address, and click **Send my invite**. WPOS then
emails them their sign-in details for your workspace.
## What the invited person sees
The invite opens a **Join** page showing the workspace, the role they are
joining as, and who invited them.
| Situation | What happens |
| - | - |
| Signed in with the invited account | They click **Accept invite** and land on the dashboard. There are no setup questions. |
| Signed in with a different account | A warning names both addresses, with **Sign in with a different account**. |
| Signed out, emailed invite | They click **Sign in to accept**, sign in, and come back to accept. |
| Already joined | The page says they are already in the workspace and offers **Go to dashboard**. |
| Invite no longer works | The page says **Invite unavailable** and why: expired, revoked, already used, or not found. They need a new invite from you. |
Once they join, they see only the sites you assign them. See
[Members](/agencies/members) for roles, budgets, and assigning sites.
## Pending invites
Invites that have not been accepted appear on the Members page under **Pending
invites**. Each shows the role, the address (for email invites), when it
expires, and **Not accepted yet**.
* **Copy link** copies the invite's joining link, so you can send it again
yourself.
* **Remove** cancels the invite at once. Its link stops working and the row
goes away.
Agency Managers see the invites they sent; the owner sees them all.
Roles, teams, credit budgets, and managing each member.
The account view your team shares at app.wpos.ai.
# Members
Source: https://docs.wpos.ai/agencies/members
Bring your team into your workspace: agency managers run a team, operators work on the sites you assign them, and everyone spends from budgets you set.
Members is how an agency brings its team into WPOS. Everyone works in the same
[Workspace](/agencies/workspace) under their own login, and each person sees
only the sites you give them. Open **Members** in the left nav at
[app.wpos.ai](https://app.wpos.ai) to manage who is on the team, what role they
hold, and how many credits they can spend.
Members is part of the paid plans. On the Free plan it does not appear in the
left nav. See [Billing](/agencies/billing).
## Roles
Every person in a workspace holds one of three roles.
| Role | Who it is for | What they see | What they manage |
| - | - | - | - |
| **Owner** | The person who created the workspace. | Every site and sandbox in the workspace. | Everyone. The owner is the only one who can make someone an Agency Manager, reset a member's password, or move an operator to another manager, and holds billing by default. |
| **Agency Manager** | A team lead who runs a group of operators. | The sites assigned to them, plus sandboxes they create. | Their own operators: inviting them, assigning them sites, and giving them credits from the manager's own budget. |
| **Operator** | The person doing the work on client sites. | The sites assigned to them, plus sandboxes they create. | Nobody. Operators do not see the Members page. |
Nobody except the owner sees a client site until it is assigned to them. A new
member with nothing assigned sees no client sites at all, so you decide
exactly which clients each person can work on.
Beyond the role, each member has their own set of permission switches, such as
whether they can add sites, create sandboxes, or reply to support tickets. Open
them from the **Permissions** arrow on the member's row. See
[Permissions](/agencies/permissions) for every switch and who can change it.
## How a team is organized
Operators report to a manager, or to you. As the Members page puts it: "An
operator works for one agency manager. Anybody with no manager reports to you."
* On the owner's Members page, each manager's row shows how many operators
report to them, for example **3 operators**. The team icon on that row
unrolls the team underneath it, and a large team gets a **View all** link to
a page of its own.
* To move an operator to another team, open the operator's three-dot menu,
choose **Reassign manager**, and pick who they report to.
* When an operator changes teams, their unspent credits go back to the manager
they are leaving (or to you), and the new manager can set them a fresh
budget.
## The Members page
Each person has a row with:
* **Name and email.**
* **Role.** A dropdown for anyone you manage. The owner can choose **Operator**
or **Agency Manager**; a manager can only set **Operator**.
* **Credit budget.** How many credits they have left and how many they have been
allotted.
* **Permissions.** The arrow that opens their permission switches.
* **Assign sites.** Choose which sites this person can see and work on. See
[Assign sites](/agencies/assign-sites).
* **Three-dot menu.** **Reassign manager**, **Reset password**, and
**Disable member**.
Anything waiting on you sits above the list: credit requests first, then access
requests. Invites that have not been accepted yet appear under **Pending
invites**, and people you have switched off appear under **Disabled accounts**.
### Adding people
Click **Add member** to open the invite screen. You can invite someone by email
or create an invite link, choose their role, and choose what they may do before
they join. Owners can invite either role; managers can invite operators. See
[Invite members](/agencies/invite-members).
## Credits for members
Members do not have credits of their own. Each one spends from a **credit
budget** that you give them out of the workspace's pool.
On the member's row, click their credit budget. The **Credits for** dialog
shows the **Total credits you can give** and how many that member has now.
Choose **Give more** to move credits from your pool into their budget, or
**Take back** to return credits they have not spent yet. Credits already
spent cannot be taken back.
An Agency Manager gives their operators credits out of the manager's own
budget, so **Total credits you can give** shows what the manager has left to
hand on.
### When a member runs low
A member asks for more with **Request Credits** at the bottom of the left nav.
They enter an amount and an optional reason, and the request goes to their
agency manager, or to the owner if they report to the owner. A member can have
one open request at a time and can withdraw it.
The request appears at the top of the approver's Members page with
**Approve** and **Decline**.
Members are not billed per seat. Adding people to a workspace costs nothing
by itself; they spend workspace credits inside the budgets you set. See
[Billing](/agencies/billing).
## Access requests
When a member meets a control they are not allowed to use, such as adding a
site or creating a sandbox, they can press **Request access**. The button
changes to **Access requested**, and the request lands in the access requests
queue on the Members page. An Agency Manager decides their own team's requests
for anything they can pass on, and the rest go to the owner. See
[When a member needs more](/agencies/permissions#when-a-member-needs-more).
## Resetting a password
The owner can issue a member a new password from their three-dot menu with
**Reset password**. Their previous password stops working immediately. The new
one is shown once, so copy it and send it to them; after that it can only be
reset again. Ask them to change it once they are in.
## Disabling and re-activating a member
Choose **Disable member** from the three-dot menu. Before anything happens, the
dialog shows exactly what will change:
* **Loses access to this workspace.** Takes effect on their next request.
* **Their sites are unassigned.** The sites stay in the workspace, ready to give
to someone else.
* **Unspent credits are returned** to whoever granted them. Credits already
spent do not come back.
* **A manager's team is handed on.** When you disable an Agency Manager, you
choose who takes their operators. The operators keep their own budgets.
* **Their login is disabled, not deleted,** if this workspace created it. A
login they had before joining, or one they also use for another workspace or
their own sites, is left switched on and only loses access to yours.
A disabled member can still sign in, but sees that their account was switched
off and cannot use WPOS. Their page unlocks by itself once they are
re-activated.
To bring someone back, find them under **Disabled accounts** on the Members page
and click **Re-activate**. They return at the role they held. Their credit
budget and site assignments are not restored, so give them those again.
Add agency managers and operators by email or with an invite link.
Choose each member's sites and give them their own WordPress login.
# Migration key
Source: https://docs.wpos.ai/agencies/migration-key
Move a site, or a sandbox, onto another WordPress install with one key: its pages, builder content, media, chats, and connectors.
Every site and every [sandbox](/agencies/sandboxes) in your
[Workspace](/agencies/workspace) has a **Migration key**. Paste it into the WPOS
plugin on another WordPress install and the whole site moves there: its pages,
its builder content, its media, and the WPOS work that goes with it.
A migration key moves a site. It does not connect one. After the move, connect
the new install to your account with your **license key**, which is a separate
step. See [Install](/get-started/install).
## Move a site
On the [Workspace](/agencies/workspace) dashboard, open the site's three-dot
menu and choose **Migration key**. Keys start with `WPOS-MIG-`.
Install the WPOS plugin on the WordPress site you are moving to, open it,
and choose **Migrate a site**.
Paste the key. WPOS asks **Migrate this site here?** and names what will be
deleted on this install before anything is sent. **Yes, migrate here**
starts the move; **Cancel** leaves everything as it is.
When the move finishes, connect the new install to your account with your
license key. Until you do, the site is there but not yet yours to operate
from the Workspace. See [Install](/get-started/install).
## What moves, and what does not
| Moves with the key | Stays behind |
| - | - |
| Pages and posts, with their builder content | The source site, untouched and still connected |
| Themes and plugins the site uses | The source site's license, which is not spent or freed |
| Media, menus, and site settings | Your account license key, which you paste separately |
| WPOS chats and connected connectors | |
The site you copy from is not changed. It keeps its license and its place in
your dashboard, so a move is a copy onto the new install rather than a handover.
Whatever is on the destination install is replaced. The confirmation names
what goes, for example its posts and pages, user accounts, and media files,
and it cannot be undone. Use a fresh install, or one you are happy to
overwrite.
## While it runs
The move runs on the new install and reports each stage as it goes: **Getting
ready**, **Installing plugins**, **Installing themes**, **Clearing this
install**, **Copying pages**, **Copying settings**, **Copying images**, and
**Finishing up**. The stages that work through a list show their progress, for
example "Copying pages, 24 of 60".
If something fails, WPOS says so on the same screen rather than leaving the
install half finished, so you can retry or ask [support](/resources/get-help)
with the detail in hand.
## Sandboxes move the same way
A [sandbox](/agencies/sandboxes) has a migration key like any other site, which
is how a build you started on a throwaway WordPress becomes the client's real
site. Build it on the sandbox, then migrate it onto their hosting and connect
it with your license key.
## Who can do it
Moving a site needs the **Migrate sites** permission, and moving a sandbox needs
**Migrate sandboxes**. Both start on for every role, so an agency manager or
operator can move a site they are assigned unless you switch it off. See
[Permissions](/agencies/permissions).
The dashboard where every site's migration key lives.
Build on a throwaway WordPress, then move it to real hosting.
Connect the new install with your license key.
Who on your team may move a site or a sandbox.
# Permissions
Source: https://docs.wpos.ai/agencies/permissions
Choose what each agency manager and operator may do in your workspace, one switch at a time, and answer their requests for more.
A member's role decides the shape of their job. Their permissions decide the
details: whether they can add sites, create sandboxes, see the plan, disconnect
a connector, and so on. Each member has their own set of switches, so two
operators on the same team can be trusted with different things.
These switches control what people can do in the Workspace at
[app.wpos.ai](https://app.wpos.ai). Which client sites a member sees is
decided separately, by [assigning sites](/agencies/assign-sites).
## How permissions work
* **The owner holds every permission.** The owner has no switches and never
needs to ask for anything.
* **Agency Managers and Operators each have their own switches.** Every switch
is on or off for that one person, whatever anyone else on their team has.
* **Operators are offered fewer switches.** The Plan switches and **Create
WordPress users** belong to an Agency Manager's job, so they are not offered
for an Operator. An Operator has 19 switches; an Agency Manager has 25.
* **Changes apply straight away.** A switch saves the moment you flip it, and
the member's dashboard updates by itself, with no need for them to sign out.
## Change a member's permissions
On the [Members](/agencies/members) page, click the **Permissions** arrow
on the member's row. The list opens under the row, headed **What (their
name) may do**.
The chips along the top show one area at a time: **All**, **Sandbox**,
**Site**, **Handoff**, **Plan**, **Connector**, **Support**, and
**Referral**. Click the
active chip again to go back to all of them.
Each switch names what it allows, with a line explaining it. Turn it on or
off; there is no Save button.
## Every permission
**New invite** is whether the switch is already on when you open the invite
screen. An invite only switches on what the person sending it holds, and the
ones marked **Off** are never switched on by an invite. You can still turn them
on yourself, in the invite or later.
### Sandbox
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **View sandboxes** | See their own sandboxes, the plugins on them, and whether they are still running. | Yes | On |
| **Create sandboxes** | Create a new [sandbox](/agencies/sandboxes), a WordPress site to test on without connecting a real one. | Yes | Off |
| **Update sandboxes** | Rename a sandbox's address, and attach or detach a custom domain. | Yes | On |
| **Migrate sandboxes** | Take a sandbox's migration key to move it, and everything on it, to another install. | Yes | On |
| **Delete sandboxes** | Destroy a sandbox and everything on it. This cannot be undone. | Yes | Off |
### Site
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **View sites** | Open the sites they are assigned and see how they are doing. | Yes | On |
| **Add sites** | Connect a new WordPress site to the workspace. | Yes | On |
| **Migrate sites** | Take a site's [migration key](/agencies/migration-key) to move it, and everything on it, to another install. | Yes | On |
| **Update the WPCursor plugin** | Press **Update now** when a new plugin release is out. | Yes | On |
| **Create WordPress users** | Create a new WordPress account on a site. Giving someone an account that already exists does not need this. | No | Off |
| **Deactivate sites** | Pause a site without removing it. Its chats, keys, and history stay where they are. | Yes | On |
| **Delete sites** | Take a site off the workspace for good. The WordPress install itself is untouched. | Yes | Off |
### Handoff
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **Handoff mode** | Put a site behind the [client handoff](/agencies/handoff) screen, or take it back out. | Yes | On |
### Plan
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **See My Plan** | See the workspace plan, credits, and usage. | No | On |
| **See billing history** | See past invoices and what the workspace has been charged. | No | On |
| **Manage payment methods** | Add or replace the card the workspace is billed on. | No | On |
| **Change the plan** | Upgrade, downgrade, or start a plan. This changes what the workspace is billed. | No | On |
| **Cancel the plan** | End the subscription. Everybody in the workspace loses access when it lapses. | No | Off |
When the owner invites an Agency Manager, the invite starts with **See My
Plan**, **See billing history**, **Manage payment methods**, and **Change the
plan** switched on. If you do not want the manager to have them, open the
**Permissions** strip on the invite screen and switch those off before you
send it.
### Connector
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **View connectors** | See which [connectors](/connect/connectors) the workspace has and which sites use them. | Yes | On |
| **Connect connectors** | Link a new third-party account, and put it on a site. | Yes | On |
| **Disconnect connectors** | Unlink a connected account. Every site using it stops being able to. | Yes | Off |
### Support
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **View support tickets** | Read the workspace's support conversations with WPOS. | Yes | On |
| **Reply to support** | Answer a support ticket. Replies go out in the workspace's name. | Yes | On |
### Referral
| Switch | What it allows | Operators | New invite |
| - | - | - | - |
| **View referrals** | See the [referral](/agencies/referrals) rewards, who signed up with the workspace's link, and the credits it has earned. | Yes | Off |
| **Share the referral link** | Copy, post, or show the QR for the workspace's invite link. Signups it brings credit the workspace. | Yes | Off |
The switches marked **Off** are the ones that end something: a sandbox, a site,
a connection, or the plan. **Create WordPress users** is off for a similar
reason: it adds a login to your client's WordPress that stays there until
someone removes it.
## What managers can pass on
An Agency Manager sets the switches for their own operators, with one rule:
**you can only pass on what you hold yourself.** The panel says so above the
switches.
* **Two exceptions.** A manager can switch on **Add sites** and **Create
sandboxes** for their operators even without holding them. The panel names
these beside the rule.
* **Switches they cannot give are greyed out.** Hovering one says "You do not
have (the permission) yourself, so you cannot give it".
* **Switching off is always allowed.** A manager can turn off anything that is
on for their own operators, even a permission they do not hold.
* **Nothing to pass on.** A manager who holds none of the switches sees a note
asking them to get them from the workspace owner first.
When the owner takes a permission away from an Agency Manager, it is taken
from that manager's operators too, except **Add sites** and **Create
sandboxes**. Switching a permission on for a manager does not switch it on for
their team; each operator gets it separately.
## When a member needs more
A control a member is not allowed to use shows a padlock and a **Request
access** button. The **My Plan**, **Connectors**, and **Support** items are
hidden from the left nav when the member cannot view them.
The member clicks **Request access**. The button changes to **Access
requested** and stays that way until someone decides.
The request appears under **Access requests** on the Members page, and in
the notification bell as "(name) asked for access to (the permission)".
**Approve** switches the permission on for them. **Decline** refuses it.
If a request is declined, hovering the button tells the member who declined
it, and they can ask again.
Who decides depends on the permission:
* **An Agency Manager answers their own team**, for anything they can pass
on: what they hold themselves, plus **Add sites** and **Create sandboxes**.
* **The owner answers the rest.** That is every request from someone who
reports to the owner, and anything a manager cannot pass on. Because the
switches that end something start off, those usually go to the owner. The
manager still sees these rows from their team, grouped as **Waiting on the
owner** and marked "owner-level, so the owner decides this one". They can
decline them but not approve them.
If an operator moves to another manager, their open requests move with them.
Switching the permission on from the member's row also closes any request
waiting for it.
## What no switch changes
Some things stay with the owner whatever the switches say:
* Making someone an Agency Manager, resetting a member's password, and moving
an operator to another manager. See [Members](/agencies/members).
* Creating a WordPress **Administrator** account on a site, even for a manager
who holds **Create WordPress users**. See
[Assign sites](/agencies/assign-sites).
Roles, teams, credit budgets, and managing each member.
Choose a new member's role and permissions before they join.
# Referrals
Source: https://docs.wpos.ai/agencies/referrals
Share your invite link and earn 5,000 credits for every person who joins WPOS and starts paying.
Every account has an invite link. Share it, and each person who signs up with it
can earn you **5,000 credits**: 1,000 when they send their first message, and
4,000 more once their first invoice clears. There is no limit on how many people
you refer.
Open **Referrals** in the left nav at [app.wpos.ai](https://app.wpos.ai).
## Share your link
Your link sits at the top of the page with a QR code beside it. Copy it, show
the QR at a meetup, or share straight to X, LinkedIn, or email.
When someone opens it, the sign-up page names who invited them and fills in the
referral code, so there is nothing for them to type.
## What you earn, and when
The referral is counted as soon as they create an account. **1,000 credits**
are released once they send their first message in chat.
**4,000 more credits** are released when their first invoice clears on
**Standard or above**. A free account, a trial, or a cheaper paid plan does
not release it, and a plan below the floor waits at **Converted to paid**
until they upgrade.
Credits land in your workspace balance and are spent like any other credits. See
[Billing](/agencies/billing).
The reward goes to the person who shared the link. The invitee gets the normal
WPOS trial, not a referral bonus.
## Follow your invitees
The page tracks the whole journey, so you know who is worth a nudge:
* **Invites sent**, **Signed up**, **Building now**, **On trial**, **Converted
to paid**, and **Credits earned**, which is what has been released to your
balance so far.
* An invitee list showing when each person joined, their status, and your
reward. Each row carries a stage: **Signed up** (created an account with your
link), **Signed up and ran a first task**, in the trial, **First invoice
cleared**, and finally **Rewarded**.
Names are shown with the email partly hidden, because anyone with dashboard
access to your workspace can read the list.
## The rules
* **A code only attaches to a brand new account.** It is captured at sign-up and
applies within the first 24 hours, so an existing customer cannot add a code
afterwards.
* **You cannot refer yourself,** or a second account on your own company's email
domain. Ordinary mailbox providers such as gmail.com do not count as the same
company.
* **One referrer per account.** Whoever's link they signed up with keeps the
referral.
* **Refunds claw it back.** A refund or chargeback within 30 days of the
invoice reverses the reward.
* **No cap.** Every paid referral pays the full amount.
## Who on your team can use it
Referrals are the workspace owner's by default. Two switches can hand them on:
**View referrals**, for the rewards and the invitee list, and **Share the
referral link**. Both start off, and an invite never turns them on by itself.
Signups brought in by a member's sharing credit the workspace, not the member.
See [Permissions](/agencies/permissions).
Plans, credits, and site licenses.
Let a team member see or share the referral link.
# Sandboxes
Source: https://docs.wpos.ai/agencies/sandboxes
Spin up a WordPress site from the Workspace to test, prototype, or demo on, with no hosting to set up. Name it, pick its plugins, and give it your own domain.
A sandbox is a WordPress site WPOS provisions for you, with the plugin already
installed and connected. Use one to try a build, prototype a layout, or demo
the agent without touching a client site or standing up hosting first.
Every sandbox is permanent: it stays until you delete it. When the work is
ready for real hosting, you can move it there with everything it holds.
## Create a sandbox
On the [Workspace](/agencies/workspace) dashboard, click **New sandbox**
next to **Add Site**.
The name becomes the sandbox's address, for example
`acme-demo.try.wpcursor.com`. Use letters, numbers, and hyphens. WPOS
checks the name as you type and marks it available or taken; when it is
taken, it suggests alternatives under **Try instead**. Leave the field
blank and click **Use default name** to get a generated name you can
change later.
Optional. Pick plugins to have them installed and activated before the
sandbox is handed over. Search the list and select up to the limit shown,
then click **Launch with N plugins**, or **Launch sandbox** to start with
none.
Progress shows each stage: **Container provisioned**, **WordPress
installed**, and **Plugins installed**. It usually takes one to two
minutes. You can close the dialog and keep working; the launch carries on
in the background.
When it finishes, **Your sandbox is ready** offers **Open WPOS chat** to start
building straight away, or **Not now**.
Creating sandboxes is for the workspace owner by default. Team members see
**New sandbox** with a padlock and can ask for it with **Request access**.
See [Members](/agencies/members).
## Working with a sandbox
The sandbox appears in your connected sites list, marked **Permanent**. Click
**Open WPOS chat** to work in its Command Center exactly like any connected
site: the agent builds, the preview updates, and credits meter the same way.
Click the sandbox's row to open its details:
* **URL**, **Username**, and **Password** for its wp-admin, each with a copy
button. The password stays hidden until you show it.
* **Plugins requested at launch**, with the result for each one.
Its menu carries the same actions as any other site, including
[handoff](/agencies/handoff), so you can hand a finished sandbox to a client
the same way.
When a team member created the sandbox, the owner sees who made it beside its
name.
## Give it a readable link
Every sandbox starts on the address it was created with. To change it, open
the sandbox's details and use **Sandbox link**: type a new name and click
**Save link**. The original link keeps working, so anything you have already
shared stays safe. **Use the original link** switches back.
## Use your own domain
You can serve a sandbox on a domain you own. In the sandbox's details, open
**Custom domain**, enter the domain, and click **Attach domain**. Then:
1. At your DNS provider, create an **A record** for the domain pointing at the
address WPOS shows you. Each address has a copy button.
2. Wait for DNS to update. Changes usually appear within minutes but can take
up to an hour. WPOS checks for you in the background, or click **Check
now**.
3. WPOS issues the certificate itself. The status moves from **Waiting for
DNS** to **Issuing certificate** to **Live**.
If something is wrong, the status shows **DNS mismatch** or **Certificate
failed**. WPOS sends an email explaining what to fix, and another when the
domain goes live. The sandbox's original URL keeps working alongside your
domain. To stop using the domain, click **Remove domain**.
## Move a sandbox to real hosting
When a sandbox is ready to become a real site, move it with its **Migration
key**:
1. Open the sandbox's menu and choose **Migration key**, then copy the key.
2. Install the WPOS plugin on the WordPress site you want to move it to.
3. In the plugin, choose **Migrate a site** and paste the key.
The sandbox moves there with its chats, connectors, and settings. Whatever was
on the destination install is replaced, so use a fresh site. The migration key
is only for moving a site; to connect a new site to your account, use your
license key instead (see [Install](/get-started/install)).
For the full walkthrough, with a video, see
[Migration key](/agencies/migration-key).
## Delete a sandbox
Open the sandbox's menu and choose **Delete sandbox**. Deleting removes the
WordPress container and the sandbox's site, including its chats, connectors,
and settings, and cannot be undone. Credits it spent stay in your account
history. Deleting is for the workspace owner by default.
Reach for a sandbox when a prospect wants to see WPOS work but you do not
want to touch their live site yet. Build the idea on a sandbox, give it a
readable link, and share it. If they sign, move it to their hosting with the
migration key.
# Workspace
Source: https://docs.wpos.ai/agencies/workspace
Your account and cross-site view at app.wpos.ai, where you manage every site you operate.
The Workspace is your account view at [app.wpos.ai](https://app.wpos.ai). Where the
[Command Center](/operate/command-center) is where you actually build, inside a
single site's wp-admin, the Workspace is the layer above it: your sites, your
account, and your billing in one place.
Keep the two straight. The Workspace (app.wpos.ai) is the account and site-list
layer. The [Command Center](/operate/command-center) is the per-site build
surface inside each site's wp-admin. The building happens in the Command Center.
## The dashboard
* **Credits Usage.** A running chart of how credits are spent across your
account, with a period selector (last 7 days, last 30 days, all time).
* **Connected sites.** Every site on your account in one list, each with when
it was added and how many credits it has used. Search filters the list once
you run more than a handful.
* **Add Site.** Connect another site: download the plugin and paste your
license key. See [Install](/get-started/install).
* **New sandbox.** Create a WordPress site to test, prototype, or demo on,
with no hosting to set up. See [Sandboxes](/agencies/sandboxes).
Each site's three-dot menu holds the rest of what you can do with it: **Open
WPOS chat**, **Migration key**, [**Handoff**](/agencies/handoff),
**Connectors**, [**Assign site**](/agencies/assign-sites), **Deactivate**
(pause it and keep its key), and **Delete Site**.
## Move a site to a new install
Every site, and every sandbox, has a **Migration key** that moves it onto
another WordPress install, with its pages, media, chats, and connectors. The
site you copy from is left as it is. See
[Migration key](/agencies/migration-key).
## Your account
The left nav carries the rest of the account:
* **My Plan** and credits. See [Billing](/agencies/billing).
* **Members**, your team: agency managers and operators, their roles, and
their credit budgets. See [Members](/agencies/members).
* **Connectors**, the account-wide integrations shared across every site.
* **Settings**, your profile, password, and your **license key** (the key you
paste when connecting a new site).
## Built for fleets
Agencies run many sites, not one. The Workspace is built for that: connect each
client site once (see [Install](/get-started/install)), then operate them all
from a single account, each site with its own context, and bring your team in
to share the work.
Bring your team in as agency managers and operators, each with a credit budget.
Invite people by email or with a one-time link.
Choose each member's sites and give them their own WordPress login.
Create a WordPress site to test, prototype, or demo on.
Plans, credits, and site licenses.
Deliver a site with WPOS locked behind your agency's brand.
# Introducing Connectors
Source: https://docs.wpos.ai/connect/connectors
Bring the tools your agency already uses into WPOS, then ask the agent to use them.
Connectors let WPOS reach the tools you already run on, beyond WordPress itself.
Connect an account once, and from then on you can ask WPOS to use that tool as part
of the work it does on a site: pull data in, push updates out, and automate steps
across your stack.
Most connectors are powered by [Composio](https://composio.dev), which gives WPOS
access to hundreds of actions across popular tools, and the catalog keeps growing.
A few, such as [Figma](/connectors/figma) and [Stitch](/connectors/stitch), are
first-party and run on the tool's own hosted server, and
[WooCommerce](/connectors/woocommerce) connects straight to the store on your own
site. Either way they behave the same once connected. Each connector below has
its own page with what it does and example requests.
## How connecting works
You connect an account once. Depending on the tool, WPOS uses one of two methods,
and it shows you which when you connect:
For tools like Google Workspace, HubSpot, or Calendly, you are sent to the
tool's own authorization screen, approve access, and you are returned to WPOS.
You never handle keys yourself.
For tools like Stripe, you paste an API key from that tool into WPOS. The
credential is stored securely as a connected account.
From your account, choose the tool and authorize it with one of the methods
above. This is a one-time setup per tool.
In the [Command Center](/operate/command-center), ask for what you want, for
example "add a JotForm contact form to this page" or "create a Stripe payment
link for this product."
## The Connectors surface
You manage connections from the **Connectors** surface in the Command Center. It
lists the available tools with their connection state, so you can connect, set up,
or disconnect each one, and search or filter the catalog by category.
* **Connected** tools are ready for the agent to use, and can be disconnected.
* **Connect** starts a one-click sign-in for tools that use OAuth.
* **Set up** collects an API key for tools that authenticate that way.
## The connector catalog
These are the connectors available today, each with how many actions it exposes.
The counts are approximate and grow over time. Click any one for its full page.
These are the connectors verified working end to end, and the set you will see
in the Connectors surface. The underlying Composio catalog is much larger and
more are being verified. If a tool you need is not here, ask at
[support@wpos.ai](mailto:support@wpos.ai).
### CRM and sales
| Connector | Actions | Example actions |
| - | - | - |
| [HubSpot](/connectors/hubspot) | 229+ | Create deal, Create email, Archive company |
| [Intercom](/connectors/intercom) | 129+ | Create contact, Block contact, Delete ticket |
### Marketing
| Connector | Actions | Example actions |
| - | - | - |
| [Mailchimp](/connectors/mailchimp) | 262+ | Add campaign, Add automation, Add export |
### Payments
| Connector | Actions | Example actions |
| - | - | - |
| [Stripe](/connectors/stripe) | 393+ | Create refund, Accept quote, Cancel quote |
| [PayPal](/connectors/paypal) | 70+ | Delete invoice, Deactivate plan, Delete template |
### E-commerce
| Connector | Actions | Example actions |
| - | - | - |
| [WooCommerce](/connectors/woocommerce) | 120+ | Create refund, Update stock, Create coupon |
### Analytics and SEO
| Connector | Actions | Example actions |
| - | - | - |
| [Google Analytics](/connectors/google-analytics) | 65+ | Get property, Get audience, Get metadata |
| [Google Search Console](/connectors/google-search-console) | 9+ | Search analytics, Inspect URL, List sitemaps |
### Productivity and documents
| Connector | Actions | Example actions |
| - | - | - |
| [Google Drive](/connectors/google-drive) | 63+ | Create comment, Create reply, Delete file |
| [Google Sheets](/connectors/google-sheets) | 50+ | Format cell, Delete sheet, Data filter |
| [Google Docs](/connectors/google-docs) | 31+ | Create header, Create footer, Delete header |
| [Notion](/connectors/notion) | 45+ | Create page, Query database, Add comment |
### Scheduling
| Connector | Actions | Example actions |
| - | - | - |
| [Calendly](/connectors/calendly) | 51+ | Get event, Create share, Get user |
| [Google Calendar](/connectors/google-calendar) | 45+ | Create event, List events, Find free slots |
### Design and forms
| Connector | Actions | Example actions |
| - | - | - |
| [Figma](/connectors/figma) | 23+ | Get component, Get file json, Delete a webhook |
| [Jotform](/connectors/jotform) | 14+ | Clone form, Create label, Get label |
| [Canva](/connectors/canva) | 46+ | List designs, Create design, Export design |
| [Stitch](/connectors/stitch) | 9 | Generate screen, Fetch screen code, Extract Design DNA |
### Messaging
| Connector | Actions | Example actions |
| - | - | - |
| [Slack](/connectors/slack) | 158+ | Send message, List channels, Fetch history |
# Calendly
Source: https://docs.wpos.ai/connectors/calendly
Calendly is a scheduling platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Calendly is a scheduling platform. Connected to WPOS, it lets the agent create scheduling links and pull event and user data to add booking to a site, all from the chat beside your site's live preview.
**Category:** Scheduling **Actions available:** 51+
## What you can do
* Get events and users
* Create share links
* Manage groups
## Example requests
Once Calendly is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Add my Calendly booking link to the contact page.
Pull this week’s Calendly events.
```
## Before you connect
* A Calendly account with at least one **event type** set up. A new account with nothing configured returns empty results.
* API access depends on your plan. Organization level data needs a paid plan.
* For team data you need **admin** or **owner** rights. A regular member sees only their own bookings.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Calendly` into **Search for integrations**, then press **Connect**.
A popup opens on Calendly. Sign in, or continue with Google or Microsoft if that is how your account was created, then approve the requested access.
Calendly sign-in can sever the popup link to WordPress, so the popup may look closed while sign-in is still running. WPOS waits up to 45 seconds, so let it finish rather than pressing Connect again.
A Composio page confirms "Successfully connected WPOS to Calendly" and returns you automatically.
The row reads **Connected** and a green "Calendly connected" banner appears in the chat.
## Troubleshooting
Expected with Calendly. WPOS keeps polling for up to 45 seconds and completes the connection if sign-in finishes.
The account has none configured, or they are all inactive. Create one in Calendly first.
You are connected as a regular member. Only admins and owners can read organization wide data, and only on plans that include it.
Calendly stores times in UTC and displays them in the viewer timezone. State the timezone you want in your prompt.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Canva
Source: https://docs.wpos.ai/connectors/canva
Canva is a drag-and-drop design suite. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Canva is where a lot of agency artwork lives. Connected to WPOS, the agent can list your designs, create new ones, and export finished artwork, which shortens the path from a design to a published page.
**Category:** Design and forms **Actions available:** 46+
## What you can do
* List and search your designs and folders
* Create designs and upload assets
* Export finished artwork for use on a site
## Example requests
Once Canva is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
List my most recent Canva designs.
Export this design and place it on the homepage.
Create a social image from our brand template.
```
## Before you connect
* A Canva account. Free works for basic use. **Canva Pro** is needed for brand kits, premium templates, and some export options.
* Know which **team** owns the designs you want. Team and brand kit features require a Canva for Teams plan.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Canva` into **Search for integrations**, then press **Connect**.
A popup opens on Canva headed "Composio would like access to your Canva account". It shows which account you are signed in as, with a **Switch accounts** link if it is wrong.
Below that, **Select a team**. Brand templates and shared assets belong to the team you pick, so choosing wrong means WPOS cannot see them.
Canva lists everything Composio will be able to do. It is a long list, so scroll through it.
Two entries change who can reach your work, not just the work itself: **change your design permissions** and **change your folder permissions**. Read them before approving, and use a test account if that is a concern.
Click **Allow**.
The row reads **Connected** and a green "Canva connected" banner appears in the chat.
## Troubleshooting
Free accounts cannot export designs containing paid stock or Pro features without purchasing them. Replace the premium elements or upgrade.
You connected a personal account, or picked the wrong team. Reconnect and select the team that owns the designs.
Large or multi page designs render asynchronously. Allow time before assuming failure.
Brand kits require Canva Pro or Teams. On a free account they do not exist.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Figma
Source: https://docs.wpos.ai/connectors/figma
Figma is a design tool. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Figma is a design tool. Connected to WPOS, it lets the agent pull components and file data to inform how a site is built, all from the chat beside your site's live preview.
**Category:** Design and forms **Actions available:** 23+
Figma is a first-party WPOS connector, not part of the Composio catalog. It
authenticates with a personal access token you generate in Figma, rather than
the managed sign-in popup most connectors use.
## What you can do
* Get components
* Read file data
* Work with webhooks
## Example requests
Once Figma is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Match this section to our Figma component.
Pull the spacing and colors from this Figma file.
```
## Before you connect
* A Figma account. Free works for personal files. Team files require the appropriate seat.
* **View access** to any file you want WPOS to read.
* The **file key** for each design, the segment after /file/ in the file URL.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Figma` into **Search for integrations**, then press **Connect**.
Instead of a popup, WPOS opens the Figma panel with an empty field labelled **Figma personal access token**. Leave it open and generate the token next.
In Figma, open **Settings**, then **Security**, and scroll to **Personal access tokens**. Click **Generate new token**. This is also where you delete the token later, which is what revokes WPOS access.
Give it a recognizable name such as `wpos`, choose an expiry, and tick at minimum `current_user:read`, `file_content:read`, and `file_metadata:read`. Add the comment scopes if WPOS should read or post comments.
Note the expiry date Figma shows. When it passes, the connection flips to Expired and you generate a fresh token. Nothing warns you in advance, so put a reminder in your calendar.
Figma shows the token **once**, with "Copy this token. This is your only chance to do so." Copy it now, because it cannot be viewed again, only replaced.
Treat this string like a password. It grants access to your Figma files as if it were you. Do not paste it into chat, email, a ticket, or a screenshot. The token in this guide is blacked out for exactly that reason.
Back in the Figma panel, paste the token into the field, where it masks as you paste, and click **Connect**.
The row reads **Connected**, with **View Details** rather than a Disconnect button. WPOS verifies the token against Figma before saving, so Connected means it genuinely works.
Click **View Details** to open the panel again. It now shows a green **Connected** badge and the **Disconnect** button.
Figma, Jotform, and Stitch are the connectors whose disconnect lives inside the detail panel rather than on the row.
## Disconnect
Press **View Details** on the row, then **Disconnect** inside the panel. The row itself has no Disconnect button.
To invalidate the token fully, also delete it in Figma, under **Settings**, then
**Security**, then **Personal access tokens**. Figma's **Connected apps** section
lists OAuth grants, and a personal access token does not create one, so deleting
the token is the whole revocation.
Disconnecting in WPOS does not revoke anything on Figma's side. Anyone holding a copy of the token could still use it until you delete it there.
## Troubleshooting
Most often the token reached the expiry date set when it was generated, and nothing warns you beforehand. It can also mean the token was revoked. Generate a new token and reconnect.
The token belongs to an account without access to that file, or the file key is wrong. Confirm the connected account can open it, and re-copy the key from the URL.
Treat it as compromised. Delete it in Settings, then Security, then Personal access tokens, generate a replacement, and reconnect. Deleting it in Figma is the only thing that actually revokes it.
Files with hundreds of frames return a lot of data. Ask for a specific frame or page rather than the whole file.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Analytics
Source: https://docs.wpos.ai/connectors/google-analytics
Google Analytics (GA4) is a web analytics platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Analytics (GA4) is a web analytics platform. Connected to WPOS, it lets the agent pull traffic, audience, and property data to inform decisions about a site, all from the chat beside your site's live preview.
**Category:** Analytics and SEO **Actions available:** 65+
## What you can do
* Read property and account data
* Work with audiences
* Pull metadata and reports
## Example requests
Once Google Analytics is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Show me the top landing pages from Google Analytics this month.
Pull the audience data for this property.
```
## Before you connect
* A **Google Analytics 4 property** that already has data. Universal Analytics properties are shut down and will not work.
* At least **Viewer** access on the property. If you cannot open it at analytics.google.com, WPOS cannot read it either.
* Popups allowed for your WordPress admin domain.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Analytics` into **Search for integrations**, then press **Connect**. The button changes to **Connecting** while the popup opens.
A popup opens on Google's account chooser, headed "to continue to Composio". Pick the account with access to your GA4 property.
If you are signed in to several Google accounts, read the addresses carefully. Connecting the wrong one is the most common mistake here, and it produces confidently wrong numbers rather than an error.
Google lists two permissions: **See and download your Google Analytics data** and **View and manage your Google Analytics data**.
The checkboxes start empty. Tick **Select all**, or both boxes individually, before continuing. Skipping this is the most common cause of a connection that completes but then returns no data.
Then click **Continue**.
A Composio page confirms "Successfully connected WPOS to Google Analytics" and reports "Taking you back to WPOS". The popup closes on its own, so do not close it manually.
The row reads **Connected** with a **Disconnect** button, and a green "Google Analytics connected" banner appears in the chat.
## Troubleshooting
The permission checkboxes at step five were left unticked. Disconnect, reconnect, and tick **Select all**.
The account has no GA4 property, or only Universal Analytics properties. Confirm a GA4 property exists at analytics.google.com.
Almost always a date range or timezone difference. GA reports in the property timezone, so state the range explicitly in your prompt.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Calendar
Source: https://docs.wpos.ai/connectors/google-calendar
Google Calendar is a scheduling and events service. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Calendar holds your schedule and events. Connected to WPOS, the agent can read availability and create events as part of the work it does on a site, which is useful for events pages and booking flows that need to respect a real calendar.
**Category:** Scheduling **Actions available:** 45+
## What you can do
* Read events across your calendars
* Create, update, and delete events
* Check availability before confirming a booking
## Example requests
Once Google Calendar is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
List my events for the next seven days.
Publish an events page from the team calendar.
Create a calendar entry for each post in the content schedule.
```
## Before you connect
* A Google account with Calendar. A free personal account is enough.
* **Make changes to events** permission on any calendar WPOS should write to.
* Know which calendar you mean. Most accounts have several.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Calendar` into **Search for integrations**, then press **Connect**.
Pick the account whose calendars you want WPOS to reach.
Google asks for two permissions: view and edit events on all your calendars, and see, edit, share and permanently delete all the calendars you can access.
The second permission includes **permanent deletion of calendars**. Read it before approving, and connect a test account first if that concerns you. The checkboxes also start empty, so tick **Select all**.
The row reads **Connected** and a green "Google Calendar connected" banner appears in the chat.
The connected row is labelled **Googlecalendar**, one word. Cosmetic only.
## Troubleshooting
The permission checkboxes were left unticked. Disconnect, reconnect, and tick **Select all**.
A timezone mismatch. State the timezone explicitly in your prompt, or set the calendar default to match your audience.
Without a calendar named, events go to the primary one. Name the target calendar in your prompt.
Google sometimes severs the popup link to WordPress. WPOS keeps waiting for up to 45 seconds, so let it finish rather than pressing Connect again.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Docs
Source: https://docs.wpos.ai/connectors/google-docs
Google Docs is a document editor. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Docs is a document editor. Connected to WPOS, it lets the agent create and edit documents and use their content on a site, all from the chat beside your site's live preview.
**Category:** Productivity and documents **Actions available:** 31+
## What you can do
* Create and edit documents
* Manage headers and footers
* Use doc content as page copy
## Example requests
Once Google Docs is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Turn this Google Doc into a blog post on the site.
Create a Google Doc with the page copy for review.
```
## Before you connect
* A Google account with Docs access. A free personal account is enough.
* **Edit** access on documents you want WPOS to modify.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Docs` into **Search for integrations**, then press **Connect**.
Pick the account whose documents you want WPOS to reach.
Google lists what Composio will be able to do, then you click **Continue**.
This screen has three shapes, depending on what Composio already holds for your account. Several new permissions gives you a checklist with **boxes that start empty**, and you must tick **Select all**. One new permission is listed plainly with nothing to tick, as pictured. Nothing new gives you just Cancel and Continue.
The row reads **Connected** and a green "Google Docs connected" banner appears in the chat.
The connected row is labelled **Googledocs**, one word. Cosmetic only.
## Troubleshooting
Docs and WordPress do not share a formatting model. Headings, bold, italics, lists, and links transfer reliably. Columns, custom fonts, text boxes, and drawings do not.
Images embedded in a doc are not added to the media library automatically. Upload them separately, or keep them in Drive and reference them from there.
WPOS reads the document body, not the comment thread. Accept suggestions before importing, or the pre-suggestion text is what gets used.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Drive
Source: https://docs.wpos.ai/connectors/google-drive
Google Drive is a file storage and sharing service. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Drive is a file storage and sharing service. Connected to WPOS, it lets the agent manage files and pull assets and content into a site, all from the chat beside your site's live preview.
**Category:** Productivity and documents **Actions available:** 63+
## What you can do
* Manage files
* Create comments and replies
* Use stored assets in a build
## Example requests
Once Google Drive is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Pull the logo from Google Drive and add it to the header.
Comment on this Drive file with the launch notes.
```
## Before you connect
* A Google account with Drive. A free personal account is enough.
* **Edit** access on folders you want WPOS to write to.
* Enough Drive storage for anything you plan to upload. A full Drive fails uploads with a quota error.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Drive` into **Search for integrations**, then press **Connect**.
Pick the account whose Drive you want WPOS to reach.
Review the request and click **Continue**.
On a first authorization Google shows permission checkboxes that start empty. Tick **Select all** before continuing. Drive access is broad: see, edit, create and delete all of your Drive files. Read it before approving.
The row reads **Connected** and a green "Google Drive connected" banner appears in the chat.
The connected row is labelled **Googledrive**, one word. Cosmetic only, as with Sheets and Docs.
## Troubleshooting
On a first authorization the permission checkboxes were left unticked. Disconnect, reconnect, and tick **Select all**.
Shared drives are separate from My Drive and need explicit membership. Confirm the connected account is a member, and name the shared drive in your prompt.
The Drive is full. Free space or upgrade before uploading.
Without a folder specified, uploads land in the Drive root. Give the folder name or its ID.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Search Console
Source: https://docs.wpos.ai/connectors/google-search-console
Google Search Console reports how a site performs in Google Search. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Search Console reports how your site performs in Google Search: the queries people search, your ranking positions, click-through rates, and indexing problems. Connected to WPOS, the agent can read that data and act on it in the same session, beside your site's live preview.
**Category:** Analytics and SEO **Actions available:** 9+
## What you can do
* Read search performance by query, page, country, and device
* Check whether a URL is indexed, and why it is not
* Pull impressions, clicks, and average position over a date range
## Example requests
Once Google Search Console is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Show me the queries where we rank on page two.
Which pages lost impressions this month?
Rewrite the meta description for our highest-impression page.
```
## Before you connect
* A **verified property** in Search Console. Unverified sites return no data.
* **Full** or **Restricted** user access on the property. Restricted users cannot submit URLs for indexing.
* Know whether yours is a domain property (covers every subdomain) or a URL prefix property (exact match only). They report different data.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Search Console` into **Search for integrations**, then press **Connect**.
Pick the account that has access to the verified property.
Review the request and click **Continue**.
This screen has two shapes. On a first authorization Google shows permission **checkboxes that start empty**, and you must tick **Select all**. Once Composio already holds access for your account it shows the shorter "Composio already has some access" screen with just Cancel and Continue, as pictured.
The row reads **Connected** with a **Disconnect** button, and a green "Google Search Console connected" banner appears in the chat.
## Disconnect
Press **Disconnect** on the row. A red "Google Search Console disconnected" banner confirms it, and the row returns to **Connect**.
## Troubleshooting
On a first authorization the permission checkboxes were left unticked. Disconnect, reconnect, and tick **Select all**.
Search Console lags by two to three days. This is Google behaviour, not a WPOS problem. Ask for ranges ending a few days back.
Domain properties aggregate every subdomain and both protocols. For one hostname only, use the matching URL prefix property.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Google Sheets
Source: https://docs.wpos.ai/connectors/google-sheets
Google Sheets is a spreadsheet application. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Google Sheets is a spreadsheet application. Connected to WPOS, it lets the agent read and write rows, format cells, and use sheet data on a site, all from the chat beside your site's live preview.
**Category:** Productivity and documents **Actions available:** 50+
## What you can do
* Read and write rows
* Format cells
* Filter data
## Example requests
Once Google Sheets is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Pull this Google Sheet into a table on the page.
Add new form submissions to my Google Sheet.
```
## Before you connect
* A Google account with Drive access. A free personal account is enough.
* **Edit** access on any sheet you want WPOS to write to.
* For Workspace accounts, third party app access must not be blocked by your admin.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Google Sheets` into **Search for integrations**, then press **Connect**.
Pick the account whose spreadsheets you want WPOS to reach.
If you have authorized Composio with this account before, Google shows "You're signing back in to Composio". Click **Continue**. First time users skip this screen.
Google asks for **two** permissions: full access to your Drive files, and full access to your Sheets spreadsheets.
Drive access is required, not optional. Spreadsheets live in Drive, so they cannot be listed, opened, or created without it. Ticking only the Sheets box leaves most operations failing. Tick **Select all**.
The row reads **Connected** and a green "Google Sheets connected" banner appears in the chat.
The connected row is labelled **Googlesheets**, one word, instead of Google Sheets. Connected rows render the raw toolkit slug rather than the catalog name. This is cosmetic and does not affect the connection.
## Troubleshooting
The permission checkboxes were left unticked, or only the Sheets box was ticked and Drive was skipped. Disconnect, reconnect, and tick **Select all**.
The sheet is not shared with the connected Google account. Share it with that address, or connect an account that already has access.
Names are not unique in Drive. Give the spreadsheet ID instead, the long string in the URL between /d/ and /edit.
A spreadsheet has multiple tabs. Name the tab explicitly in your prompt.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# HubSpot
Source: https://docs.wpos.ai/connectors/hubspot
HubSpot is an inbound marketing, sales, and CRM platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
HubSpot is an inbound marketing, sales, and CRM platform. Connected to WPOS, it lets the agent capture leads from your site into HubSpot, create deals and contacts, and trigger follow-up emails, all from the chat beside your site's live preview.
**Category:** CRM and sales **Actions available:** 229+
## What you can do
* Create and update contacts and companies
* Create deals and move them through stages
* Create and send marketing emails
* Archive records you no longer need
## Example requests
Once HubSpot is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
When someone submits the contact form, create a HubSpot contact and a new deal.
Create a HubSpot email campaign for the leads we captured this month.
```
## Before you connect
* A HubSpot account. The free CRM tier works for contacts and companies. Deals and tickets depend on your subscription.
* **Super Admin**, or a user with permission to install integrations.
* Know **which HubSpot account** you mean, and create it before you start rather than during the flow.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `HubSpot` into **Search for integrations**, then press **Connect**.
A popup opens on HubSpot's OAuth Bridge, headed "Connecting your Composio account to HubSpot", with two buttons: **Sign in to your HubSpot account** and **Create a new HubSpot account**.
Sign in with email and password, or continue with Google. With Google, its account chooser appears headed "to continue to HubSpot".
Agencies often hold several HubSpot accounts. Read the account name carefully, because writes go to a live CRM and cleaning up records created in the wrong account is manual work.
If you chose to create a new account, HubSpot runs its signup wizard. It pre-fills a profile and asks "Does this look right?" for company name, website, size, and industry.
This is where the connection can go astray. The signup wizard is HubSpot's own flow, not part of the OAuth handshake, so finishing it can land you in the HubSpot dashboard with the connection never completed. If that happens, close the popup, return to Connectors, and press **Connect** again. The second attempt takes the sign-in path and completes normally. Creating the HubSpot account **before** starting avoids this entirely.
HubSpot asks which account to connect and lists the CRM scopes. Approve it, and the row reads **Connected** with a green "HubSpot connected" banner in the chat.
The connected row is labelled **Hubspot**, with a lower case s. Connected rows render the raw toolkit slug rather than the catalog name. Cosmetic only.
## Troubleshooting
The most damaging mistake here, because writes go to a live CRM. Disconnect immediately, reconnect, read the account name, and clean up any records created in the wrong account.
Expected when you create the account mid flow. Close the popup, return to Connectors, and press **Connect** again.
Your subscription tier does not include the object you are reaching for. Deals and tickets need paid tiers.
Custom properties use internal names that differ from their display labels. Look up the internal name in Settings, then Properties.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Intercom
Source: https://docs.wpos.ai/connectors/intercom
Intercom is a customer messaging and support platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Intercom is a customer messaging and support platform. Connected to WPOS, it lets the agent create and manage contacts, and handle support tickets from the work you do on a site, all from the chat beside your site's live preview.
**Category:** CRM and sales **Actions available:** 129+
## What you can do
* Create, archive, and block contacts
* Create and delete tickets
* Keep support data in step with site activity
## Example requests
Once Intercom is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Create an Intercom contact when someone submits the demo form.
Open an Intercom ticket from this support request.
```
## Before you connect
* An Intercom workspace. Intercom has no free tier, so an active subscription or trial is required.
* Permission to install apps. Workspace admins can, teammates with restricted roles usually cannot.
* Know your **data host region**. It is set per workspace and must match at authorization time.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Intercom` into **Search for integrations**, then press **Connect**.
A popup opens on Intercom's sign in. Signing in with Google shows the account chooser headed "to continue to intercom.com".
Google confirms what intercom.com will receive: your name, profile picture, and email address. Click **Continue**.
Intercom lists every scope Composio is requesting, including Tickets, AI content, Custom objects, and Jobs. Scroll through, then click **Authorize access**.
Check the **data host region** at the bottom of this screen: United States, Europe, or Australia. It must match the region your Intercom workspace lives in, or the connection authorizes against the wrong region and returns no data.
The row reads **Connected** and a green "Intercom connected" banner appears in the chat.
## Troubleshooting
The data host region selected at authorization does not match your workspace region. Disconnect, reconnect, and set the correct region.
Your teammate role does not allow installing apps. Ask a workspace admin.
The Articles product is not enabled on your plan. Check your subscription in Intercom.
Intercom archives older conversations and the API paginates. Narrow the date range rather than asking for everything.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Jotform
Source: https://docs.wpos.ai/connectors/jotform
Jotform is an online form builder. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Jotform is an online form builder. Connected to WPOS, it lets the agent build and manage forms for lead capture and embed them on a site, all from the chat beside your site's live preview.
**Category:** Design and forms **Actions available:** 14+
## What you can do
* Clone forms
* Create and manage labels
* Use forms for lead capture
## Example requests
Once Jotform is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Add a Jotform contact form to this page and style it to match.
Clone this Jotform for the new landing page.
```
## Before you connect
* A Jotform account. The free tier works, with limits on forms and monthly submissions.
* A **Jotform API key** with **Full Access** if you want WPOS to create or edit forms.
* Check your Jotform region. Accounts on EU or HIPAA data residency use different API endpoints.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Jotform` into **Search for integrations**. The button on the row reads **Set up**, not Connect. That is how WPOS marks a connector that needs your own credentials.
A two column dialog appears, headed "Connect Jotform". The form is on the left, and Composio's Jotform documentation is embedded on the right.
| Field | What it does |
| - | - |
| **Available on** | **This site only** keeps the credential to this WordPress install. **All sites in this account** shares it with every site on your WPOS account |
| **Label** (optional) | A name to recognise this credential by later |
| **API key** | Your Jotform API key, generated next |
In Jotform, go to **My Account**, then **API**, and click **Create New Key**. The key appears in the table, and unlike Figma it stays visible so you can copy it again later.
Check the **Permissions** column. A new key defaults to **Read Access**, which is enough to search and fetch but not to create or edit forms. Raise it to **Full Access** if you want WPOS to build forms.
Back in WPOS, paste the key into **API key**, where it masks as you type, then click **Save & connect**.
Because this is an API key rather than OAuth, there is no authorization popup and nothing to approve. WPOS stores the credential and completes the connection immediately.
The row reads **Connected** with **View Details**, and a green "Jotform connected" banner appears in the chat.
## Disconnect
Press **View Details** on the row, then **Disconnect** inside the panel. The row itself has no Disconnect button. Your forms and submissions stay in Jotform, and only the stored key is removed.
To invalidate the key entirely, also delete it in Jotform under **My Account**, then **API**. Disconnecting in WPOS does not revoke it on Jotform's side.
## Troubleshooting
The most common Jotform problem. New keys default to **Read Access**, and nothing in WPOS warns you. The connection reports Connected either way, because the key is valid, just limited. Raise the key to Full Access in Jotform and reconnect.
Correct behaviour. Jotform has no managed authorization, so WPOS asks for credentials before it can connect anything.
Check for whitespace copied along with the key, confirm the key still exists, and confirm your account region matches.
Treat it as compromised. Delete it in My Account, then API, create a replacement, and reconnect.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Mailchimp
Source: https://docs.wpos.ai/connectors/mailchimp
Mailchimp is an email marketing platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Mailchimp is an email marketing platform. Connected to WPOS, it lets the agent add subscribers from your site forms and build campaigns and automations, all from the chat beside your site's live preview.
**Category:** Marketing **Actions available:** 262+
## What you can do
* Add and manage audiences
* Create campaigns
* Set up automations
* Export audience data
## Example requests
Once Mailchimp is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Add form subscribers to my Mailchimp audience.
Create a Mailchimp welcome automation for new signups.
```
## Before you connect
* A Mailchimp account with at least one **audience**. Without one there is nothing to add subscribers to.
* **Admin** or **Manager** role. Viewer and Author roles cannot modify audiences or send campaigns.
* Your **server prefix**, the region code in your Mailchimp URL, for example `us19`.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Mailchimp` into **Search for integrations**, then press **Connect**.
Mailchimp is the one connector that asks for something before sign-in. A Composio page headed "WPOS wants to connect to your Mailchimp" asks for a **Server Prefix**.
This is the region code in your Mailchimp URL. If you log in at `us19.admin.mailchimp.com`, the prefix is `us19`. It is also the part after the dash in your API key.
Get this wrong and the connection authorizes against the wrong Mailchimp region, so it completes but returns nothing. Copy it from your browser address bar rather than guessing.
Click **Connect Account**.
Mailchimp shows "Log in and authorize". Enter your username and password, or continue with Google if that is how the account was created.
If two factor authentication is on, have your authenticator ready. A slow second factor is what usually times the flow out.
Signing in with Google shows the account chooser headed "to continue to mailchimp.com".
The popup closes on its own. The row reads **Connected** with a **Disconnect** button, and a green "Mailchimp connected" banner appears in the chat.
## Troubleshooting
The server prefix is wrong. Log in to Mailchimp and copy the region code from the address bar, then disconnect and reconnect with the correct value.
Create one in Mailchimp first, under Audience, then Create Audience.
The audience uses double opt-in, so Mailchimp sends a confirmation and waits. This is correct behaviour, not a fault.
Your role lacks permission, or the sending domain is not authenticated in Mailchimp.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Notion
Source: https://docs.wpos.ai/connectors/notion
Notion is a workspace for notes, docs, wikis, and databases. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Notion is where a lot of agencies plan content. Connected to WPOS, the agent can read and write the pages and databases you share with it, so a content calendar in Notion can drive what gets published on the site.
**Category:** Productivity and documents **Actions available:** 45+
## What you can do
* Read pages and databases you have shared
* Create and update pages
* Read and post comments
## Example requests
Once Notion is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Publish this Notion page as a blog post.
Read the content calendar database and draft the next three pages.
Mark this row as published in Notion.
```
## Before you connect
* A Notion account. A free personal workspace is enough.
* Decide upfront **which pages** to share. Notion authorization is page by page, not workspace-wide.
* Adding more pages later means editing the integration access in Notion.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Notion` into **Search for integrations**, then press **Connect**.
A popup opens on Notion's login page. Enter your email and click **Continue**, or use Google, Apple, Microsoft, Passkey, or SSO.
If you log in with Google, its account chooser appears headed "to continue to Notion".
Notion shows "Connect with Composio". Pick the workspace from the dropdown and review what Composio will be permitted to do: view, edit, and create content in **pages you select**, view and create comments, and view workspace users and their emails.
Click **Select pages to access**.
Add every page or database WPOS should reach. Selecting a parent page includes its children.
Anything you do not add here is invisible to WPOS. This is the biggest difference between Notion and the other connectors: Notion grants access page by page, never workspace-wide. It is also the most common cause of "page not found" later.
Click **Allow access**.
The row reads **Connected** and a green "Notion connected" banner appears in the chat.
## Troubleshooting
It was not shared with the integration. In Notion, open the page, then the more menu, then **Connections**, and add WPOS. Or disconnect and reconnect, ticking more pages.
A page created outside a shared parent is not shared automatically. Create pages inside an already shared parent, or share each new one explicitly.
Relations, rollups, and formulas have no WordPress equivalent. Title, text, select, date, number, and checkbox transfer reliably.
Headings, paragraphs, lists, quotes, code, and images transfer well. Toggles, synced blocks, columns, and embeds do not.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# PayPal
Source: https://docs.wpos.ai/connectors/paypal
PayPal is a payments and invoicing platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
PayPal is a payments and invoicing platform. Connected to WPOS, it lets the agent manage invoices, plans, and billing templates, all from the chat beside your site's live preview.
**Category:** Payments **Actions available:** 70+
## What you can do
* Create and delete invoices
* Manage subscription plans
* Work with billing templates
## Example requests
Once PayPal is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Create a PayPal invoice for this client.
Deactivate this PayPal subscription plan.
```
## Before you connect
* A PayPal account, and the credentials PayPal issues for it. PayPal is not a one-click connector, so you supply your own credentials rather than signing in through a popup.
* Decide **which sites** the credential should serve before you open the dialog, because that is the first thing it asks.
* Know **which PayPal account** you mean. Agencies often hold several, and these actions create invoices and manage subscription plans against a live account.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Type `PayPal` into **Search for integrations**. The row reads "Online payment system that allows money transfers and ...".
The button on the PayPal row reads **Set up**, not **Connect**.
That difference is the whole story. **Connect** means WPOS runs a managed sign-in popup, as Stripe does. **Set up** means the connector has no managed authorization, so WPOS asks you for credentials first. PayPal is a **Set up** connector, like [Jotform](/connectors/jotform).
A two column dialog opens, headed "Connect PayPal" with the line "Provide credentials to enable this integration". The form is on the left, and a **Setup guide** panel is embedded on the right, with **Open in new tab** if you want it full size.
The form starts with the same two fields every Set up connector uses:
| Field | What it does |
| - | - |
| **Available on** | **This site only** keeps the credential to this WordPress install. **All sites in this account** shares it with every site on your WPOS account |
| **Label** (optional) | A name to recognise this credential by later |
Below those sit the PayPal credential fields themselves. The **Setup guide** panel on the right of the dialog tells you exactly which values PayPal issues and where to generate them, so work from that panel rather than from memory.
Click **Save & connect**. Because this is a credential rather than a managed sign-in, there is no authorization popup and nothing to approve. **Cancel** closes the dialog without storing anything.
## Troubleshooting
Correct behaviour, not a fault. PayPal has no managed authorization in WPOS, so it asks for credentials before it can connect anything.
Costly here, because PayPal actions create invoices and manage subscription plans on a live account. Disconnect, then reconnect with the credentials for the right account.
Treat them as compromised. Revoke them in PayPal, issue replacements, and reconnect. Disconnecting in WPOS does not revoke anything on PayPal's side.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Slack
Source: https://docs.wpos.ai/connectors/slack
Slack is a channel-based messaging platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Slack is where most teams already talk. Connected to WPOS, the agent can read and post messages, which is the usual way to get a site to tell your team something without anyone checking a dashboard.
**Category:** Messaging **Actions available:** 158+
## What you can do
* Post messages to public and private channels
* Read channel history for context
* List channels, users, and conversations
## Example requests
Once Slack is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Post to #general when this page goes live.
Send new form submissions to #leads.
Summarise what was decided in #website this week.
```
## Before you connect
* A Slack workspace you can install apps into. Owners and admins can install directly, members may need approval.
* Know which **channel** you want WPOS to post to, and whether it is public or private.
* If your workspace requires admin approval for new apps, request it before starting.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Slack` into **Search for integrations**, then press **Connect**.
A popup opens on Slack's "Sign in to your workspace" page. Enter your workspace URL, the `your-workspace` part of `your-workspace.slack.com`, and click **Continue**. If you are already signed in, your workspaces are listed under "You're already signed in to".
Pick carefully if you belong to several workspaces. Connecting the wrong one means WPOS posts into the wrong company Slack, and the only undo is to disconnect and reconnect.
Slack lists what the app may do, including reading channel content and posting messages. Review and click **Allow**.
The row reads **Connected** and a green "Slack connected" banner appears in the chat.
Public channels need nothing further. For a **private** channel, open it in Slack and run `/invite @WPOS`. Without this, posting to a private channel fails with a not-in-channel error.
## Troubleshooting
The app has not been invited to that private channel. Run `/invite @WPOS` inside it.
Either the channel name is wrong, or it is private and the app cannot see it. Use the exact name without the hash, and confirm the app was invited.
Your workspace restricts app installs. Ask an owner or admin to approve the request, then connect again.
The popup used a workspace you were already signed in to. Disconnect, then reconnect and read the workspace carefully.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Stitch
Source: https://docs.wpos.ai/connectors/stitch
Stitch is Google's AI UI design tool. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Stitch is Google's AI UI design tool. Connected to WPOS, the agent can generate
screens from a prompt, read the code and styles behind an existing screen, and
carry a design's fonts, colors, and layout into the site it is building.
**Category:** Design and forms **Actions available:** 9
Stitch is a first-party WPOS connector, not part of the Composio catalog. It
runs on Google's own hosted Stitch MCP server, so its tools update as Google
ships them. Stitch is in **beta**, so expect the interface to move.
## What you can do
* Generate new UI screens from a text prompt, straight from chat
* Browse your Stitch projects and screens
* Pull a screen's frontend code and high resolution screenshot for implementation
* Extract Design DNA, meaning fonts, colors, and layout, to keep new work on style
## Example requests
Once Stitch is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
List my Stitch projects.
Generate a pricing page screen in Stitch, then build it on this site.
Pull the Design DNA from that screen and apply it to the homepage.
```
## Before you connect
* A Stitch account at [stitch.withgoogle.com](https://stitch.withgoogle.com).
* A **Stitch API key**, created from your profile menu. The steps below cover it.
* No popups needed. Stitch takes a key directly, with no OAuth round trip.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Every available connector is listed alphabetically, each with a button showing what it needs.
Type `Stitch` into **Search for integrations**, then press **Connect** on the row.
Instead of a popup, WPOS opens the Stitch panel with a single field for your API
key, plus the feature list, notes, and the nine tools it exposes.
Leave it open and create the key next.
In [stitch.withgoogle.com](https://stitch.withgoogle.com), open your profile
picture, then **Stitch settings**, then **API key**. Click **Create key**.
Stitch runs abuse auto-detection: any key it finds publicly exposed is
disabled automatically. That protects you, but it also means a key pasted
into a public repo or a shared screenshot stops working without notice.
The new key appears in the table with its creation date and last used time.
Stitch only ever displays the last few characters of a key, so this page
cannot leak the full value later. Copy it when it is first shown. If you
lose it, delete the key and create another.
Back in the Stitch panel, paste the key into the field, where it masks as you
paste, and click **Connect**.
The row then reads **Connected** with **View Details**, and Stitch's nine tools
become available to the agent.
Like Figma and Jotform, Stitch keeps **Disconnect** inside the detail panel.
Click **View Details** to reach it. The row itself has no Disconnect button.
## Tools
| Tool | What it does |
| - | - |
| `list_projects` | List all your available Stitch projects |
| `get_project` | Retrieve details of a specific project |
| `create_project` | Create a new Stitch project |
| `list_screens` | List all screens within a project |
| `get_screen` | Get metadata for a specific screen |
| `generate_screen_from_text` | Generate a new screen from a text prompt and design context |
| `fetch_screen_code` | Download the frontend code of a screen |
| `fetch_screen_image` | Download the high resolution screenshot of a screen |
| `extract_design_context` | Scan a screen to extract its Design DNA: fonts, colors, layouts |
## Disconnect
Press **View Details** on the row, then **Disconnect** inside the panel. The
stored key is removed from WordPress. Your Stitch projects and screens are
untouched.
To invalidate the key entirely, also delete it in Stitch under **Stitch
settings**, then **API key**, using the delete icon on the row. Disconnecting in
WPOS does not revoke it on Google's side.
## Troubleshooting
Stitch disables keys it detects as publicly exposed. If the key was ever pasted
into a public repo, a ticket, or a shared screenshot, assume it was caught.
Create a new key and reconnect.
Stitch shows only the last few characters after creation, so there is nothing
to recover. Delete the key and create another.
The account behind the key has no Stitch projects yet. Create one at
stitch.withgoogle.com first, then ask again.
Correct behaviour. Stitch, Figma, and Jotform all keep Disconnect inside the
detail panel. Click **View Details** to reach it.
Expected. Stitch runs on Google's hosted MCP server, so the tool set follows
what Google ships rather than the WPOS release cycle. Stitch is in beta, so
this happens more often than with other connectors.
For issues shared across every connector, see
[Introducing Connectors](/connect/connectors).
## Related
* [Figma](/connectors/figma), for reading designs you have already built
* [Canva](/connectors/canva), for finished artwork rather than UI screens
# Stripe
Source: https://docs.wpos.ai/connectors/stripe
Stripe is a payments platform. Connect it to WPOS and the agent can use it as part of the work it does on your site.
Stripe is a payments platform. Connected to WPOS, it lets the agent create payment links and products, issue refunds, and manage quotes and subscriptions, all from the chat beside your site's live preview.
**Category:** Payments **Actions available:** 393+
## What you can do
* Create refunds
* Accept and cancel quotes
* Manage products, prices, and subscriptions
* Handle invoices and payments
## Example requests
Once Stripe is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
Create a Stripe payment link for this product.
Issue a Stripe refund for this order.
```
## Before you connect
* A Stripe account. Create it before you start rather than during the flow, so you know which account you are connecting.
* The **email address on that Stripe account**. Stripe's own hint on the form: "Have a Stripe account? You can use the same email address."
* Stripe requires the form to be completed by the **business owner**, or by someone with **significant management responsibility** for that business. If that is not you, hand this step to whoever it is.
## Connect it
In WordPress admin, open **WPOS**. You land on the chat screen.
Click the **+** button to open the launcher panel, then choose **Connectors**.
Type `Stripe` into **Search for integrations**. The row reads "Stripe offers online payment infrastructure, fraud prevention, and ...", with a **Connect** button on the right.
Press **Connect**. The button changes to **Connecting...** and a popup opens on Stripe's own domain, `connect.stripe.com`. Leave the WPOS tab alone while the popup is open, because the row stays on **Connecting...** until the popup finishes.
The popup is headed "Composio partners with Stripe for secure payments" on the left, and **Get started with Stripe** on the right. Enter your **Email address**, then press **Continue**.
This page is served by Stripe, not by WPOS. The "Powered by Stripe" mark, the Terms and Privacy links, and the `connect.stripe.com` address are all Stripe's. If you want to back out, use **Return to Composio** rather than closing the window.
Stripe states that if you are completing this form on behalf of a business, it must be completed by the business owner or someone with significant management responsibility for that business. Agencies often hold several Stripe accounts, so check the email address before you continue. This connection can move real money.
Complete the remaining Stripe prompts and approve the connection. When the popup closes, the Stripe row in **Connectors** stops reading **Connecting...** and shows the connection.
## Troubleshooting
The popup is still open, or it was blocked before it opened. Finish it or close it, then press **Connect** again.
The costly mistake here, because Stripe actions touch live payments. Disconnect, reconnect, and check the email address on the Stripe screen before you press Continue.
Stripe requires an owner, or someone with significant management responsibility, to complete the form. Pass this step to that person rather than working around it.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# WooCommerce
Source: https://docs.wpos.ai/connectors/woocommerce
WooCommerce is the store plugin for WordPress. Connect it to WPOS and the agent can read and manage the store's products, orders, and customers.
WooCommerce is the store plugin for WordPress. Connected to WPOS, the agent can
manage the store's products, orders, customers, and coupons, and pull its sales
reports, all from the chat beside your site's live preview.
**Category:** E-commerce **Actions available:** 120+
WooCommerce is a first-party WPOS connector. It connects straight to the store
on your own site with a WooCommerce REST API key, and WPOS creates that key for
you. Each store keeps its own key.
## What you can do
* **Products:** list, create, update, duplicate, and delete products, manage
their variations, and update stock
* **Catalog:** manage categories, tags, brands, and product attributes with
their terms
* **Orders:** look up, create, and update orders, add order notes, and record
refunds
* **Customers:** look up, create, and update customers, and see their
downloads
* **Coupons and reviews:** create and manage coupons, and moderate product
reviews
* **Shipping and tax:** manage shipping zones, methods, and classes, and tax
rates and classes
* **Reports:** sales, revenue, and order stats, top sellers, and stock,
customer, coupon, and tax reports
* **Store setup:** read and change store settings and payment gateways, manage
webhooks, and check system status
## Example requests
Once WooCommerce is connected, ask in the [Command Center](/operate/command-center):
```text theme={null}
List the products that are out of stock.
Show me this week's orders.
Which products sold best this month?
Create a 15% off coupon called SPRING15 that ends on 30 April.
```
## Before you connect
* **WooCommerce** installed and active on the site.
* **WPOS plugin v2.2.99 or later** for one-click connect. On an older plugin,
update it or paste keys manually (see below).
* **Handoff mode off** on that site if you connect from the Workspace. A site in
[handoff](/agencies/handoff) refuses the connect until handoff is turned off.
## Connect it
There are two ways to connect, and both end with the same result: a WooCommerce
REST API key (read/write) that WPOS creates on the store itself, so there is
nothing to copy.
In WordPress admin, open **WPOS**, click the **+** button to open the
launcher panel, and choose **Connectors**.
Find **WooCommerce** in the list and connect it. WPOS generates a
WooCommerce REST API key on the site, owned by your admin account, and
checks it against the store.
The WooCommerce row reads **Connected**, and the agent can use the store
straight away.
In the [Workspace](/agencies/workspace), open **Connectors** and click
**Connect** on **WooCommerce**. A panel lists every site in your
account.
Click **Connect** next to the store. WPOS asks that site's plugin to
create its own key, and a message confirms **WooCommerce connected on**
the site. Your other sites are unaffected.
Every store has its own key, so connect each one separately. A site
without WooCommerce installed is skipped.
### Paste keys manually instead
Use this only when a site cannot create its own key, for example because its
plugin is too old.
1. In WordPress admin, go to **WooCommerce > Settings > Advanced > REST API**
and click **Add key**. Give it **Read/Write** permission.
2. In the Workspace, open WooCommerce under **Connectors** and choose **Paste
keys manually instead**.
3. Paste the **Consumer key** (`ck_…`) and **Consumer secret** (`cs_…`).
Pasted keys are stored for every site in your account. That is right when you
run one store, and wrong when your sites are different shops: the agent would
read the wrong store's data. For separate shops, connect each site on its own.
## Disconnect
* **In the WPOS plugin:** disconnecting removes the WPOS key from the store's
**WooCommerce > Settings > Advanced > REST API** list, so old keys do not pile
up.
* **From the Workspace:** **Disconnect** next to a site deletes that store's
keys only. **Disconnect all** clears the credential on your account and on
every site.
## Troubleshooting
The site is in [handoff](/agencies/handoff), which blocks connecting from the
Workspace. Turn handoff off in the WPOS panel on that site, connect
WooCommerce, then turn handoff back on.
One-click connect needs WPOS plugin v2.2.99 or later. Update the plugin on
that site, or paste keys manually.
The site does not have WooCommerce installed and active, or it is not an
active site in your account. Install and activate WooCommerce, then connect
again.
Keys were pasted manually, so every site in the account shares one store's
credentials. Disconnect, then connect each store from its own site.
For popup failures, expired connections, and other issues shared across every
connector, see [Introducing Connectors](/connect/connectors).
# Install the plugin
Source: https://docs.wpos.ai/get-started/install
Install WPOS on a WordPress site and connect it to your account.
WPOS runs as a WordPress plugin paired with the WPOS backend. The plugin gives
you the Command Center inside wp-admin and keeps the site in sync with your
account.
## Requirements
WordPress 6.0 or newer (WordPress 7.0 fully supported)
PHP 7.4 or newer
Ability to install plugins (administrator access)
A WPOS account at [app.wpos.ai](https://app.wpos.ai)
## Install
WPOS is distributed through your account, not the public WordPress.org directory.
Download the latest WPOS plugin ZIP from your account at
[app.wpos.ai](https://app.wpos.ai).
In wp-admin, go to Plugins, then Add New, then Upload Plugin, and select the
ZIP.
Activate the plugin. A WPOS menu item appears in the wp-admin sidebar.
Open the WPOS panel and connect the site to your account. The site is now
linked to your [Workspace](/agencies/workspace), and the
[Command Center](/operate/command-center) is ready to use.
## Updates
WPOS ships updates through the normal WordPress update flow. When a new version
is available, you will see it on the Plugins screen like any other plugin.
Running many client sites? See [Workspace](/agencies/workspace) for managing
updates and connections across your whole fleet from one place.
## Troubleshooting
Confirm the plugin is active under Plugins, and that your user has
administrator capabilities. Clear any full page cache and reload wp-admin.
Check that the site can reach the WPOS backend over HTTPS and that no security
plugin is blocking outbound requests. Re-open the WPOS panel and retry the
connection.
# Onboarding
Source: https://docs.wpos.ai/get-started/onboarding
Watch the full WPOS onboarding walkthrough, and see what happens when you create your account: workspace setup, a few questions, and the dashboard tour.
New to WPOS? Start with the onboarding walkthrough: a full tour of the
platform, from creating your account to a finished site.
## Create your account
Go to [app.wpos.ai](https://app.wpos.ai) and create an account with your
name, email, and a password, or click **Continue with Google**. If you sign
up with email, WPOS sends a verification link; open it to confirm your
address before you sign in.
Next, WPOS asks how you will use it:
* **For my agency**: collaboration features and shared context.
* **Just for me**: personal usage and individual settings.
Pick **For my agency** if you run client work with a team.
WPOS asks a few short questions so it can fit the workspace to you. The
agency path covers how you found WPOS, your team size, your role, the
services you provide, your ideal client, the hosting you use, and your
toolkit. **Just for me** asks three of them. Use **Next** to move on and
**Back** to change an earlier answer. Your progress is saved, so you can
finish later from where you left off.
The welcome screen confirms your workspace is ready. Click **Continue to
workspace** to open the dashboard.
## Take the dashboard tour
The first time you open the dashboard, WPOS offers a short tour:
* **Quick tour**: sandboxes and sites, setting one up, and running it.
* **Full tour**: every page of the workspace, one at a time.
* **Maybe later**: skip it for now.
The tour only shows the parts of the workspace your role can use. To take it
again, open **Settings**, find **Product tour**, and click **Take a tour**.
## Next steps
Connect your first site and make your first change.
Get WPOS onto a site and connected to your account.
Your sites, account, and billing in one place.
Bring your team into the workspace.
# Quickstart
Source: https://docs.wpos.ai/get-started/quickstart
Connect your first site and make your first change in under ten minutes.
This guide takes you from nothing to your first change on a live WordPress site.
WPOS end to end: a brand PDF, a Figma design, connectors, native Gutenberg and Elementor builds, and versioned deploys, all on real WordPress.
You need a WordPress site you can install a plugin on, and a WPOS account. If you
do not have an account yet, sign up at [app.wpos.ai](https://app.wpos.ai).
Install and activate the WPOS plugin on your site. Full instructions, including
the manual upload path, are in [Install the plugin](/get-started/install).
Open the WPOS panel in wp-admin and connect it to your account. WPOS links the
site to your [Workspace](/agencies/workspace) and reads how it is built.
In wp-admin, open the WPOS menu. This is the
[Command Center](/operate/command-center): a chat on one side and a live
preview of your site on the other.
In the preview, click the element you want to work on, for example a heading,
a button, or a whole section. WPOS attaches that element to your message so it
knows exactly what you mean. This one habit is the difference between a vague
result and the right one.
With the element attached, type what you want, for example:
```text theme={null}
Make this heading larger and add a little space below it.
```
WPOS makes the change and you see it on the preview. Keep going in the same
chat: each request builds on the last.
Start small and specific. "Make this button bigger" on a clicked element works
better than "redesign my homepage" in one shot. Get one section right, then move
to the next.
## Next steps
Learn the chat-and-preview workflow in full.
The page-by-page workflow agencies use to ship a site.
Bring tools like forms into the work.
Manage many sites from one place.
# Welcome to WPOS
Source: https://docs.wpos.ai/index
WPOS builds and operates client WordPress sites with AI agents. Work in a chat beside a live preview, point at anything, and ask for the change in plain language.
WPOS is the AI-native operating system for WordPress, built for agencies. You work in a chat that sits right next
to a live preview of your site, inside wp-admin. Click any element in the preview,
say what you want in plain language, and WPOS makes the change: building sections
and pages, editing content, adjusting layout and styling, writing the CSS or code,
and wiring up forms.
It works on one site through the [Command Center](/operate/command-center) and
across many sites through the [Workspace](/agencies/workspace). This is the
operator manual: connecting a site, working in the Command Center, building and
styling pages, wiring up forms and the tools your agency already uses, and
operating WPOS from your own agent over MCP.
## What WPOS is not
WPOS is not an AI website builder that spits out a generic site and locks you
inside a closed platform. The sites stay native WordPress, on your host, in your
control. The point is not to generate a site once and walk away. The point is to
operate sites continuously: the same chat-and-preview loop handles the first
build, the client's later requests, and the upkeep months down the line.
## How it fits together
Install the plugin on a WordPress site and link it to your WPOS account. WPOS
reads how the site is built. See [Install](/get-started/install).
Work from the Command Center inside wp-admin. Click an element in the preview,
ask for the change, and watch it land on the page. See
[The Command Center](/operate/command-center).
Bring in the tools you already use, like forms and the rest of your stack,
through [Connectors](/connect/connectors).
## Start here
Connect your first site and make your first change in under ten minutes.
Get WPOS onto a site and connected to your account.
Chat plus a live preview inside wp-admin. Click an element to work on it.
Bring tools like forms and the rest of your stack into the work.
## Built for agencies
WPOS is built for WordPress delivery agencies that build and maintain client sites at scale. Whether you run one site or fifty,
the operating model is the same: connect a site, then operate it from the chat
beside its live preview. If you run many sites, the
[Workspace](/agencies/workspace) is the cross-site view, and
[Billing](/agencies/billing) covers plans, credits, and site licenses.
# Skills
Source: https://docs.wpos.ai/memory/skills
Specialized, reusable abilities the site's agent can run, built in or your own.
Skills are specialized abilities the site's agent can use. Some are built in, and
you can add your own. They are how the agent gets reliably good at a specific job,
like building a UI, researching in the browser, or summarizing activity from a
connected tool, instead of figuring it out from scratch each time.
## Built-in and custom
Ship with WPOS, ready to use. Examples include browser research, a component
library precondition, and connector activity summaries.
Your own. Add a skill folder with a starter `SKILL.md` file to teach the agent
a procedure specific to how your team works.
## Working with skills
* **Browse and search** installed and recommended skills, and filter by category.
* **Add a new skill** by creating a skill folder with a `SKILL.md` that describes
the procedure.
* **Reuse across sites.** A skill captures how a job should be done so the agent
repeats it the same way, rather than re-deriving it every time.
Skills are where your agency's repeatable IP lives. The more you encode the way
your team does a task into a skill, the more consistent the agent's output
becomes across every site you operate.
# Build a page
Source: https://docs.wpos.ai/operate/build-a-page
The page-by-page workflow agencies use to ship a site with WPOS: one page right, then the next.
WPOS works best the way a good designer works: get one page right, then carry that
look across the rest. This page describes the workflow agencies actually use to
ship client sites.
## Work one page at a time
Want to agree the layout before any design work? Start with a
[wireframe](/operate/wireframes), then build the finished page on the same
structure.
Do not ask WPOS to build a whole site in a single instruction. Get the **homepage**
exactly right first. Once it is right, move to the next page, and the next. Each
page is faster because the look is already settled.
Build and refine the homepage section by section, clicking each element in the
preview and asking for the change. See [The Command Center](/operate/command-center).
Get spacing, type, and brand right on the homepage. This becomes the reference
for every other page. See [Styling and brand](/operate/styling).
"Let's move on to the next page." Apply the same look to the about page, the
services page, and so on, one at a time.
Add the forms, the lead capture, and any tools the site needs. See
[Forms](/operate/forms) and [Connectors](/connect/connectors).
## Reuse the client's existing content
If you are rebuilding or restyling an existing site, keep the client's words. Tell
WPOS to pull the content and images from the current site and only redesign the
layout, rather than rewriting copy you did not want touched.
```text theme={null}
Rebuild this section using the design we just made, but keep the exact copy and
images from the existing page at https://theirsite.com/about.
```
Be explicit that the copy stays word for word. WPOS will happily rewrite text if
you do not tell it to preserve it.
## Review on staging, then go live
Build and review on your staging or preview URL first. When a page is right,
publish it. After launch, do a quick pass to confirm everything moved over.
Walk the page on the preview URL. Check the layout, links, images, and mobile.
Publish when you are happy. Ask WPOS to push the page live.
Confirm the live URLs resolve, the forms submit, and any redirects work. See
[Forms](/operate/forms) for testing submissions and thank-you redirects.
Lead generation and a working contact path are usually the highest priority on a
client site. Test the form and its redirect before you call a launch done.
# Page builders
Source: https://docs.wpos.ai/operate/builders
WPOS works in the builder the site already uses: Gutenberg, Elementor, or Divi, with no lock-in to a builder of its own.
WPOS does not replace the site's page builder or lock you into one of its own.
It works in whatever the site already runs: Gutenberg, Elementor, or Divi. What
the agent builds shows up as native content in that builder, so a client (or
another developer) can keep editing it the normal way after you hand off.
## Pick your builder
Core blocks in the block editor, and sections turned into reusable widgets.
Real Elementor containers you can edit in the Elementor editor.
Divi pages from a design's own code or in native modules, on Divi 4 and 5.
## Builder independence
WPOS widgets are registered natively in every builder it supports, so the same
widget works wherever the site is built:
* **Gutenberg.** Native blocks, under a **WPOS** category in the block
inserter, and in the **Patterns** panel.
* **Elementor.** Native Elementor widgets, under their own **WPOS** category in
the widget panel, with no compatibility warning in the editor.
* **Divi.** Native Divi modules through the Builder Bridge, on Divi 4 and
Divi 5.
## Choose the builder WPOS targets
In [Settings](/operate/settings), under **Preferences**, set **Preferred page
builder** to **Gutenberg** or **Elementor**. The change takes effect from the
next chat session. On a Divi site, say in the chat that you want the page
built in Divi.
Pages built from the builder's own blocks, containers, and modules stay
editable in that builder even without WPOS. Sections built as WPOS widgets
need the WPOS plugin to render, so keep the plugin active on sites that use
them.
## The Full Width template
WPOS ships a builder-independent **Full Width** page template that renders a
page with no theme header or footer. It shows up in the WordPress page
attributes template dropdown, so it works even on sites without a builder's own
canvas template.
## WooCommerce
On a WooCommerce store, WPOS can customize the store templates (product pages,
cart, checkout, archives) from [chat](/operate/chat), without editing the
theme's files, and it warns you when something else already controls a
template. See [WooCommerce](/operate/woocommerce).
# Chat and modes
Source: https://docs.wpos.ai/operate/chat
How the WPOS chat works: modes, auto plan, voice, attachments, context, conversations, and tasks.
The chat is the heart of the [Command Center](/operate/command-center). You ask in
plain language, the agent acts on the site, and the preview updates. A few controls
shape how it works.
## Modes
The mode sets how the agent approaches a request. You can change it per chat, and
set a default in [Settings](/operate/settings).
The default. A good balance of speed and depth for everyday changes.
More thorough reasoning for harder or higher-stakes work.
## Controls
* **Auto plan.** When enabled in [Settings](/operate/settings), the agent shows a
plan before executing complex tasks, so you can review the steps before it
touches the site.
* **Voice input.** Dictate your request instead of typing. See
[Build by voice](/operate/voice).
* **Attach.** Add a file to your message, or select an element in the preview
to attach it. See [Select and edit](/operate/select-and-edit).
* **Context window.** Each conversation has a context budget. You can clear context
or compact a long conversation from [Settings](/operate/settings) when it gets
heavy.
## Conversations and tasks
Every conversation is a **task**, kept in the task list on the left so you can
reopen it and pick up where you left off. You can drag an earlier task onto the
chat box to use it as context, and follow a longer run step by step in the
Tasks panel above the chat box. See [Tasks](/operate/tasks) for all of it.
Start in Balanced for routine edits and switch to Expert when a task is tricky or
important. Turn on Auto plan when you want to see the agent's intended steps
before anything changes.
# The Command Center
Source: https://docs.wpos.ai/operate/command-center
Chat plus a live preview inside wp-admin. Click an element, ask in plain language, watch the change land.
The Command Center is the WPOS surface inside wp-admin. It is two things side by
side: a **chat** where you ask for what you want, and a **live preview** of your
site. You point at something in the preview, describe the change in plain
language, and WPOS makes it. You stay in the same chat as you go, and each request
builds on the last.
## The layout
Your real site, rendered right there. Click any element, a heading, a button,
an image, a whole section, to work on it.
Where you ask. Plain language, no commands to memorize. "Make this full width."
"Add more space below this section." "Change this button text."
## The one habit that makes it work: click first, then ask
This is the single most important thing to learn. Before you type, **click the
element in the preview you want to change.** WPOS attaches that exact element to
your message, so it knows precisely what "this" means.
Turn on **Inspect element** in the chat box, then click the heading, button,
image, or section in the live preview. It becomes the subject of your next
message. See [Select and edit](/operate/select-and-edit) for the details.
Type the change in plain language, for example "make this heading larger" or
"remove this" or "add a little space below it."
WPOS makes the change and the preview updates. If it is not quite right, say so
in the same chat and refine.
Asking "make the header bigger" without clicking the header first gives WPOS no
context and usually a vague result. Click the element first, every time. That
habit is the difference between frustrating and effortless.
## What you can do here
* Build and edit sections and pages.
* Edit content and copy.
* Adjust layout, spacing, and styling, including custom CSS. See
[Styling and brand](/operate/styling).
* Build and style forms, and test the submission. See [Forms](/operate/forms).
* Bring in tools you already use through [Connectors](/connect/connectors).
## Working well with it
Keep requests small and specific. "Make this button bigger" on a clicked element
beats "redesign my homepage" in one shot. Get one section right, then move to the
next. See [Build a page](/operate/build-a-page) for the full workflow.
A bigger build can take a little while to complete. If it is working on something
substantial, give it a moment, and prefer breaking a large ask into smaller, scoped
requests rather than one giant instruction.
# Divi
Source: https://docs.wpos.ai/operate/divi
WPOS builds real Divi pages, from a design's own code or in native Divi modules, and its widgets work as native modules in Divi 4 and Divi 5.
On a site running Divi, WPOS builds real Divi pages. Give it a design and it
can assemble the page from that design's own code, or build it again in
native Divi modules. Its widgets show up in the Divi Builder like any other
module.
The **Preferred page builder** setting in [Settings](/operate/settings)
offers Gutenberg and Elementor. On a Divi site, say in the chat that you want
the page built in Divi.
## Two ways to build a Divi page
Give WPOS a design, such as a [Stitch](/connectors/stitch) artboard, and it
assembles the page from the design's exported HTML and CSS instead of
redrawing it, so the result matches the design.
Ask for the page in native Divi modules and it builds one you can edit
module by module in the Divi Builder.
In the video, WPOS does both from one Stitch artboard:
* It copies the design's images into the media library first, because the
original image links would expire.
* It fixes what WordPress and Divi change on save, such as extra paragraph
tags, stripped form fields, and Divi styles overriding the design's own.
* Before the native rebuild, it says which parts of the design have no native
Divi equivalent, and builds the native version on its own page instead of
replacing the one that already works.
```text theme={null}
Build this Stitch design as a page in Divi, then build
the same page again in native Divi modules.
```
## WPOS widgets in Divi
* **Native Divi modules.** Every WPOS widget is registered as a Divi module,
on both **Divi 4** (classic, shortcode-based pages) and **Divi 5**
(block-based pages).
* **In the Visual Builder.** On Divi 4, add and edit WPOS modules in the
Visual Builder like any other module.
* **Same output everywhere.** A widget renders the same HTML on Divi 4 and
Divi 5, and the same widget also works in Gutenberg and Elementor.
How WPOS works across page builders, with no lock-in.
Build with core blocks and reuse sections as widgets.
# Elementor
Source: https://docs.wpos.ai/operate/elementor
WPOS builds real Elementor pages you can open and edit in the Elementor editor, and its widgets sit in the Elementor widget panel like any other.
On an Elementor site, WPOS builds real Elementor pages: containers and widgets
you can open in the Elementor editor and change the normal way, on your own
theme, with images in your own media library.
## Set the site to Elementor
In [Settings](/operate/settings), under **Preferences**, set **Preferred page
builder** to **Elementor**. The change takes effect from the next chat
session, and WPOS builds in Elementor from then on.
## What WPOS builds
* **Real Elementor pages.** Every section is an Elementor container you can
edit in the Elementor editor afterwards.
* **WPOS widgets in the widget panel.** Every WPOS widget is registered as a
native Elementor widget, under its own **WPOS** category, and renders in the
editor with no compatibility warning.
* **A safety copy before each rewrite.** Elementor does not keep a WordPress
revision when a page's layout is saved this way, so WPOS keeps copies of a
page's recent layouts before it rewrites them. An edit that goes wrong can
be rolled back.
## From a reference site to a finished page
In the video, WPOS rebuilds the look of an inspiration site from one link and
a two-word constraint, "no gradients":
* It studies the reference and describes what it sees before building.
* It copies the photos into the media library instead of linking to the
original site.
* It sets up the theme's colors as variables, so recoloring the whole site is
one edit.
* It adds scroll animations on request, finds and fixes a bug in its own
animation code, and checks every animated element on the page before
reporting done.
```text theme={null}
Rebuild the look of this site in Elementor, no gradients:
https://example.com
```
A page whose sections show up empty after a migration usually uses a WPOS
widget that did not come across to the new site. WPOS names the missing
widget in a note visible to anyone signed in with edit rights (in the page
source), so you know what to bring over.
How WPOS works across page builders, with no lock-in.
Open-source tooling for building Elementor sites as code.
# Files
Source: https://docs.wpos.ai/operate/files
Every asset on the site in one place: images, audio, videos, fonts, documents, and code the agent can use.
The Files surface is the asset library for a site inside the
[Command Center](/operate/command-center). It is where the images, videos,
fonts, and documents the site uses live, so the agent can pull from them as it
builds.
## What you see
Files are grouped by type, each group with a count of what is in it.
Every image on the site, ready to drop into a page.
Audio files the site uses.
Video assets the site uses.
Font files uploaded to the site. Elementor lists them in its font picker,
even without Elementor Pro.
PDFs and other documents, including things you want the agent to reference.
Code WPOS has written for the site. The only group that is not an upload.
## Working with files
* **Search** the library to find an asset fast.
* **See where a file came from.** Each file is marked **Uploaded** (added by
you, from the media library or the workspace) or **Generated** (produced by
WPOS in chat). Use the filter beside the search box to show **All**, only
**Uploaded**, or only **Generated** files.
* **Reference assets in chat.** Ask the agent to use a specific file by name, for
example "use the hero image from Files on the homepage."
* **Bring assets in from a connector.** Pull files from a tool like Google Drive
through [Connectors](/connect/connectors), then use them in a build.
Uploading your brand assets (logo, hero images, product shots) into Files first
means you can refer to them by name later instead of re-uploading them each time.
# Forms and lead capture
Source: https://docs.wpos.ai/operate/forms
Build, style, and embed a form with WPOS, then test the submission and the thank-you redirect before you launch.
For most client sites, a working contact path is the highest-value thing on the
page. WPOS builds and styles forms, embeds them, and connects them to the form
tools you already use. The rule that matters: do not call a form done until you
have tested a real submission and the redirect.
## Build and style a form
Click where the form should go in the preview, then ask. You can use a native
WordPress form or connect a tool like JotForm through [Connectors](/connect/connectors).
```text theme={null}
Add a contact form to this section with name, email, and message fields, and
style it to match the rest of the page.
```
Style the form like any other element: click it in the preview and ask for the
spacing, field sizes, and button to match your brand. See
[Styling and brand](/operate/styling).
## Test the submission and redirect
This is the step people skip and regret. After the form is in place, submit it
yourself and confirm what happens next.
The form needs a live or staging URL to accept a real submission.
Fill the form in and submit it the way a visitor would.
Check that the submission landed where it should, in WordPress, in your form
tool, or wherever you route entries.
Make sure the visitor is sent to the right thank-you page after submitting, and
that page is published.
A form that looks right but does not deliver the lead is worse than no form. Test
the full path, submission to thank-you page, before you go live.
## Where forms fit in a launch
Forms are part of the page-by-page build and the post-launch check. See
[Build a page](/operate/build-a-page) for the full ship-a-site workflow, and
[Connectors](/connect/connectors) for wiring a form tool into WPOS.
# Gutenberg
Source: https://docs.wpos.ai/operate/gutenberg
Build with core WordPress blocks, keep every section editable in the block editor, and turn any section into a reusable WPOS widget.
On a site built with the block editor, WPOS builds with Gutenberg's own core
blocks: cover, columns, quote, and group. Nothing proprietary, so every
section stays editable the normal way when you or your client open the
editor.
## Set the site to Gutenberg
In [Settings](/operate/settings), under **Preferences**, set **Preferred page
builder** to **Gutenberg**. The change takes effect from the next chat
session, and WPOS builds in the block editor from then on.
## What WPOS builds
* **Core blocks.** Pages are made of the block editor's own blocks (cover,
columns, quote, group), not wrappers that only WPOS understands.
* **Editable in the editor.** Open the page in the block editor and every
section is a real block you can change by hand.
* **Styling kept to the page.** When a page needs its own styling, WPOS can
keep it in a snippet scoped to that page, so it cannot leak into the rest of
the site.
```text theme={null}
Build a services page from core blocks: a cover hero,
a grid of six services, two quotes, and a call to action.
```
## Turn a section into a reusable widget
When a section should be reused or edited by someone who does not touch
markup, ask WPOS to make it a widget. For example, a service grid can become a
widget with a repeater, so services are added, reordered, and edited from
fields instead of code.
```text theme={null}
Make the services grid reusable, so I can add
and reorder services without editing markup.
```
WPOS widgets fit into the block editor like any other block:
* **Native blocks.** Every WPOS widget is registered as a block. Search the
block inserter and it appears under the **WPOS** category.
* **Block patterns.** Every widget is also available from the editor's
**Patterns** panel.
* **Not tied to one builder.** The same widget also works in Elementor and
Divi. See [Page builders](/operate/builders).
How WPOS works across page builders, with no lock-in.
Build real Divi pages and use WPOS widgets as Divi modules.
# Select and edit
Source: https://docs.wpos.ai/operate/select-and-edit
Point at what you want changed. Pick up to five elements in the live preview, add a note to each, and WPOS edits them at the source.
The fastest way to change something on a page is to point at it. Turn on the
inspector, click the elements you mean, and your instruction travels with them,
so WPOS knows exactly what "this" is. One message can carry **up to 5
elements**, so several fixes go in together.
## Select an element
In the chat box of the [Command Center](/operate/command-center), click
**Inspect element**. In the preview, the cursor becomes a crosshair and
elements outline as you move over them. Click the button again (**Disable
inspector**) to turn it off.
Click the heading, button, card, image, or section you want to change. It
gets a numbered pin on the page, its **Comment** box opens on that pin, and
it appears as a numbered pill in the chat box, attached to your next
message.
Type the change for that one element in its **Comment** box and press
**Enter**, or click **+**, to attach the note to its pill. Or close the box
and write a single message in the chat box for everything you picked.
```text theme={null}
Make this heading larger and add a little space below it.
```
## Select up to 5 elements at once
Keep clicking to pick more, for example two buttons that should line up, or
three cards that should match. Each new pick gets the next number.
* **Numbered pins.** Each pick gets a pin on the page, numbered 1 to 5 in the
order you picked them, so you can refer to them in your message: "make 2
match 1".
* **A note per element.** The **Comment** box that opens on a new pick takes a
note for that element alone. **Enter** or **+** attaches it. A pin carrying a
note turns blue, and clicking it reopens the note to edit. **Remove this
element** in the box drops the pick.
* **Click again to deselect.** Clicking a pick again removes it, unless it
carries a note: then it reopens the note instead, so you cannot lose what you
wrote by clicking twice. Drop a pick that has a note with the **x** on its
pill.
* **Selection full.** At 5, nothing new highlights: the cursor shows a blocked
badge where you click, and a message says you can select up to 5 elements at
once. Remove one to pick another.
In the chat box, each pill shows its number and the element's tag and class,
for example ``. The **x** on a pill removes that pick, and a
pill carrying a note opens to show it in full when you click it. **Clear all**
appears once there is more than one thing attached, and clears the whole
message: every pick and any files with them.
```text theme={null}
Give 1 and 2 the same height, and style 3 like 1.
```
Sending empties the selection, so the next message starts clean.
## What WPOS does with your selection
* **Gets the whole picture.** Every pick travels with its selector, tag,
classes, text, size and position, the widget it belongs to, and your note for
it, so the agent does not have to guess which "this" you mean.
* **Finds the source.** WPOS traces the element back to the widget and the
style rule behind it, and makes the change there, instead of patching the
page on top.
* **Checks the result.** It looks at the live page after the change to confirm
it landed before it reports back.
* **Tells you what it found.** If something gets in the way, such as a
conflict with another rule, it says so plainly rather than hiding it.
If the result is not quite right, reply in the same chat. The element stays in
context, so "a little smaller" is enough.
Select first, then ask. "Make the header bigger" without a selection leaves
WPOS guessing which header you mean. With the element selected, the same
sentence lands exactly where you intended.
The chat and live preview where you select and edit.
Speak your request instead of typing it.
# Settings
Source: https://docs.wpos.ai/operate/settings
Per-site WPOS preferences: default mode, auto plan, context, preferred page builder, and your account.
Settings is where you tune how WPOS behaves on a site and manage your account. It
opens as a surface in the [Command Center](/operate/command-center) with tabs for
**Preferences**, **Account**, **Usage**, and **Handoff**.
## Preferences
* **Default mode.** The mode applied to new conversations: **Balanced**,
**Expert**, or **Expert 1M**. See [Chat and modes](/operate/chat) for what each
one is for.
* **Auto plan.** When on, WPOS shows a plan before executing complex tasks, so you
can review the intended steps before it touches the site.
* **Context window.** Shows the tokens consumed in the current session, with
options to **Clear context** or **Compact conversation** when a long chat gets
heavy.
* **Preferred page builder.** Tell the agent whether to prioritize **Gutenberg**
or **Elementor**. Takes effect on the next chat session.
## Account, Usage, and Handoff
Your account details for this site.
How credits are being used. See [Billing](/agencies/billing) for how credits,
plans, and site licenses fit together.
Hand a site or its access off, for client or team workflows.
Set the **Preferred page builder** to match how the site is actually built. It
keeps the agent producing markup that fits the site instead of mixing builders.
# Site tree
Source: https://docs.wpos.ai/operate/site-tree
See the whole site layer by layer: pages, components, and snippets, with a live preview of any of them.
The Site tree is the structural view of a site inside the
[Command Center](/operate/command-center). It shows the whole site, layer by
layer, so you can navigate by structure instead of hunting through wp-admin.
## What you see
Every page on the site. Expand one to see its sections and the components
inside it.
The reusable building blocks used across the site, in one place.
Saved pieces you can reuse anywhere, kept with the site.
## Working in the tree
* **Search the site.** Use the search box to find any page, section, or component
quickly.
* **Preview anything.** Select a page and it loads in the live preview beside the
tree, so you see exactly what you are working on.
* **Switch to Code.** Toggle the Code view to see the underlying markup for what is
selected.
* **Point and ask.** Once a page is in the preview, click an element and ask for a
change, the same [click-then-ask](/operate/command-center) loop as everywhere
else.
The Site tree is the fastest way to move around a large site. Find the page in
the tree, load it in the preview, then make your change, instead of clicking
through the front end to get there.
# Styling and brand
Source: https://docs.wpos.ai/operate/styling
Keep sites on-brand with WPOS: pin your colors, type, and logo, and fix sizing with targeted CSS.
WPOS will style a site for you, but it does not invent your brand. For agency-grade
results you tell it your standards explicitly, and you keep control of the details
that matter most: color, type size, spacing, and the logo.
## Start from your brand guidelines
You do not have to type your brand out. Attach the guidelines themselves, a PDF,
a document, or an image, to your message and tell WPOS to build from them.
* **The values become the site's tokens.** WPOS reads the colors and fonts out
of the document and sets them as the site's design tokens, which WordPress
picks up through the theme, so every section inherits them instead of
carrying its own copied hex codes. Change a token later and the whole site
follows.
* **Contrast is checked as it goes.** WPOS tests the pairings it uses and says
when one does not hold up, for example giving an amber button navy text
rather than white.
* **It asks instead of guessing.** When the guidelines leave out something it
cannot invent, such as what the business actually does, it stops and asks.
* **Invented content is flagged.** Anything it fills in, such as example stats
or testimonials, is marked as placeholder copy to replace before the page
goes live.
```text theme={null}
Use the attached brand guidelines as the site's brand, then build the landing page.
```
## Pin your brand explicitly
WPOS does not auto-apply a brand kit. Give it your exact specifications and it will
hold to them. The more precise you are, the less you repeat yourself.
```text theme={null}
Use these brand colors: primary #1D2327, accent #3858E9, text #4A4A4A.
Headings in Inter Tight, body in Inter. Use our real logo, not an icon plus text.
```
Hand it hex codes, not color names. "Make it blue" drifts; "#3858E9" does not.
The same goes for fonts and the actual logo file.
## Fix sizing with targeted CSS
A common surprise: WPOS tends to build text smaller than an agency would. The fix
is to tell it your target sizes in pixels, and apply them to specific elements
rather than blanketing the whole page.
When a theme or builder fights you on specificity, a targeted rule with
`!important` on a specific selector wins without affecting everything else:
```css theme={null}
#ewb-block-1 .cpl-topbar a {
font-size: 1.25rem !important;
}
```
Then ask WPOS to do the same for the other elements you care about:
```text theme={null}
The text is too small. Set the top bar links, buttons, and eyebrow labels to the
sizes above, using targeted CSS on each element rather than a global override.
```
Prefer targeted selectors over a site-wide font-size override. A global change
has side effects you will spend longer undoing than the original fix.
## Work element by element
Brand and styling fixes are exactly where the click-first habit pays off. Click the
heading, the button, or the section in the live preview, then ask for the size,
color, or spacing change. WPOS knows precisely which element you mean. See
[The Command Center](/operate/command-center).
Once the homepage looks right, reuse it as the reference for every other page so
the whole site stays consistent. See [Build a page](/operate/build-a-page).
# Tasks
Source: https://docs.wpos.ai/operate/tasks
Every conversation with WPOS is a task. Start one, come back to it, reuse an earlier task as context, and follow a run while it works.
Everything you ask WPOS happens inside a **task**. A task is one conversation:
your requests, the agent's replies, and the work it did on the site. The task
list on the left of the [Command Center](/operate/command-center) keeps every
task you have run on this site, so you can reopen one, pick up where you left
off, or hand an earlier task to WPOS as context for a new one.
## Start a new task
**New task** sits at the top of the left sidebar. It opens an empty chat
with the **WPOS at your service!** greeting.
Type or [speak](/operate/voice) your request and send it. Pick the mode
(Balanced or Expert) in the chat box first if you want a different one. See
[Chat and modes](/operate/chat).
Your first message creates the task. After WPOS answers, it names the task
from what you asked, for example "Create Dark Mode Bag Sale Landing Page",
and the task appears in the list.
One task per job. A focused task stays fast and cheap to continue. When you
move on to something unrelated, start a new task instead of adding it to an
old one. You can still bring the old one along as context (see below).
## Your task list
The **All tasks** section of the sidebar lists your tasks on this site. The
task you have open is highlighted.
* **Reopen a task.** Click it. The full conversation comes back and you can
keep going from where you stopped.
* **Rename a task.** Hover over it and click the pencil. Type the new name,
press **Enter** to save or **Esc** to cancel.
* **Delete a task.** Hover over it and click the bin, then confirm. This
permanently deletes the conversation and all its messages. It cannot be
undone.
* **See older tasks.** Scroll to the bottom of the list and click **Load
more...**.
* **Make room.** The icon at the top of the sidebar collapses it to a narrow
strip. **New task** and the context ring stay available there.
### Find a task
To search instead of scrolling, open a new tab in the right panel with
**+** and type in the **Find anything on this site** box. Matching tasks
appear under **Tasks** (up to eight, matched on the task name). Click one to
open it. The same search also finds steps of the task that is running right
now.
## Use earlier tasks as context
You can hand WPOS the history of earlier tasks so it knows what was already
decided or built, without pasting anything.
Drag any task from **All tasks** and drop it on the chat box. It appears
above your message as a chip with the task's name.
Drag in up to **three** tasks for one message. Remove one with the **x**
on its chip.
For example: "Build the About page in the same style as these two." The
chips stay on your sent message, so you can always see which tasks it
used.
What WPOS reads from a referenced task:
* **The most recent part of the conversation.** Up to the last 60 messages,
and roughly 12,000 characters per task. For a long task, that is the latest
stretch, which is usually the part that matters.
* **A record, not the live site.** WPOS treats it as what was said at the
time, and checks the site itself before relying on it. If the page has
changed since, the live page wins.
* **Only your tasks on this site.** You cannot reference a task from another
site, or the task you are already in (its context is already there).
A referenced task adds to the size of your message, so reference the tasks
that matter for this request rather than all of them.
## While a task is running
### The Tasks panel
For bigger requests, WPOS plans the work as a list of steps. The plan shows
in the **Tasks** panel just above the chat box, collapsed to a single line
such as **Tasks · 3 active · 1 running**. Click it to expand the list:
* The step being worked on sits at the top, marked **In progress**.
* Steps still to come are next, marked **In Queue**.
* Finished steps move to the bottom, marked **Completed** with a green ring.
The header stays in view while the list scrolls, so you always know how much
is left. Open a step to see the smaller actions it took along the way, each
with how long it ran.
The panel goes away on its own as soon as every step is done, and it stays
hidden if you stop the run. A small request that needs no plan never shows
it.
### Keep talking, stop, or switch
* **Add to the running task.** Send a message while WPOS is working and it
goes into the run in progress, so you can steer it ("use the brand green
instead") without waiting. Files go into the run with it. A referenced task
is the one thing that cannot join a run already in progress: a message
carrying one is held and sent as soon as the run finishes.
* **Stop it.** Click **Stop** in the chat box, or press **Esc** while you are
typing in it. The Tasks panel clears for that run.
* **Switch to another task.** A running task keeps working while you look at
something else. Open it again from the list and its progress is there.
* **Reload the page.** WPOS reconnects to the task that was running and
carries on showing its progress.
## When a task finishes
A banner above the chat box says whether the task **completed** or
**failed**, with a short summary. If you let your browser show
notifications for this site (WPOS offers to turn them on), you also get a
desktop notification, **Task completed** or **Task failed**, so you can work
in another tab while a long task runs.
## The context meter
At the bottom of the sidebar, **Context: X%** shows how much of the task's
context window is used: everything WPOS has read in this task so far, against
the most it can hold at once. Next to it is the same figure in tokens, for
example `48k / 200k`.
The ring beside it changes colour as the task fills up:
| Ring | Context used | What it means |
| - | - | - |
| Grey | Up to 50% | Plenty of room. |
| Orange | Over 50% | A long task. Consider starting a fresh one for new work. |
| Red | Over 80% | Nearly full. WPOS will soon need to compact it. |
When a task fills up, WPOS **compacts** it: you see a **Compacting...**
notice, then **Context compacted**, and the task carries on with a summary of
what came before instead of every message. You can also do this yourself, or
clear the context completely, from **Settings, Preferences, Context window**.
See [Settings](/operate/settings).
A task that keeps growing costs more to continue, because WPOS reads more
of it with every message. For a new job, start a **New task** and drag the
old one in as context. You keep the history without carrying all of it.
# Build by voice
Source: https://docs.wpos.ai/operate/voice
Speak your request instead of typing it. WPOS transcribes it into the chat box so you can check it, then builds it on the live page.
Sometimes it is faster to say what you want than to type it. Speak your brief
into the chat, check the transcript, and send it. WPOS builds it on the live
page exactly as if you had typed it.
## Speak a request
With the chat box empty, click the microphone button at the right of the
chat box. (The same button becomes **Send** once there is text in the
box.) The first time, your browser asks for permission to use the
microphone. Allow it.
A waveform shows WPOS is listening. Describe the change the way you would
type it, for example:
```text theme={null}
Add a full-width call to action at the bottom:
dark green, one headline, one button to the contact page.
```
Confirm to place the transcript in the chat box, or cancel to throw it
away.
Read the transcript, fix anything that was misheard, and send it.
Once sent, a spoken request works like any other: WPOS checks the site,
builds on the live page, and reports back in the chat.
## Tips
* **Speak in English.** Voice input transcribes US English.
* **Be as specific as you would be in writing.** Name the section, the
colors, and what the button should do. A clear sentence out loud gives the
same result as a clear sentence typed.
* **Use a supported browser.** Voice input uses your browser's built-in speech
recognition. Chrome, Edge, and Safari support it. If the microphone button
does nothing, check the microphone permission for your wp-admin site, or
try one of those browsers.
Modes, auto plan, attachments, and the rest of the chat.
Point at an element in the preview and change it.
# Wireframes
Source: https://docs.wpos.ai/operate/wireframes
Sketch first, build fast: ask WPOS for a grey-block wireframe to agree the layout, then turn the same structure into the finished page.
Before you commit to a full design, ask WPOS for a wireframe: a grey-block
version of the page with every section in place. Use it to agree the
structure with your client while changes are still cheap. Once the layout is
right, WPOS builds the finished page on the same structure.
## Ask for a wireframe
In the [Command Center](/operate/command-center) chat, ask for a
**wireframe**. Asking for a prototype, a lo-fi mockup, a rough layout, or a
quick mock works the same way. WPOS then builds a wireframe instead of a
finished design.
```text theme={null}
Build a wireframe of a services page: a hero, three
service cards, pricing, testimonials, and a contact form.
```
```text theme={null}
Give me a quick mock of the About page layout
before we design it.
```
WPOS places the wireframe on a real page of the site, so you review it in the
live preview like any other page, then walks you through what it built.
## What a wireframe looks like
* **Greyscale only.** Structure and hierarchy are shown in shades of grey, with
no colour or brand styling, so nobody mistakes a wireframe for the final
design.
* **Real labels, placeholder copy.** Headings, navigation, labels, and buttons
use real text. Body copy appears as grey placeholder lines, and images as
crossed-out placeholder boxes.
* **Real page sections.** Navigation, hero, cards, forms, tables, tabs,
accordions, and footers are laid out as real sections, not a flat picture.
* **Responsive.** On screens 768px wide or narrower, multi-column rows stack
into a single column, so you can check the mobile layout too.
* **Works in Gutenberg and Elementor.** A wireframe looks the same in the
preview, on the front end, and inside the Gutenberg and Elementor editors.
The whole wireframe is built as a single WPOS widget, which is what makes it
fast to build and easy to replace once the real design is ready.
## From wireframe to finished page
Open the wireframe in the preview and change the layout in plain language,
for example "move pricing above testimonials" or "add an FAQ section".
Settle the structure here, before any styling.
When the layout is approved, ask WPOS to design it, for example "apply the
brand to this page". WPOS builds the finished page on the same structure.
See [Styling and brand](/operate/styling) for pinning the brand.
Ask WPOS to keep the wireframe on its own page and build the designed
version separately, so you can compare the two side by side.
Wireframes use placeholder content. Replace placeholder text, sample forms,
and example testimonials with the client's real content before you publish.
The page-by-page workflow for shipping a site.
Pin the brand so every page comes out on-brand.
# WooCommerce
Source: https://docs.wpos.ai/operate/woocommerce
Restyle a WooCommerce store from chat: product pages, cart, checkout, and archives, without editing the theme's files.
On a WooCommerce store, WPOS customizes the store's templates (product pages,
cart, checkout, and archives) from [chat](/operate/chat). It checks how the
store is actually built first, picks the approach that will take effect, and
tells you when an instruction would not have worked.
## How WPOS changes store templates
WPOS stores its template customizations itself and renders them at
runtime, so it never edits the theme's files. Update or switch the theme
and your customizations are unaffected.
WooCommerce renders these stores from block templates, so WPOS edits the
block template, the same way the Site Editor does. A PHP template override
would not take effect there.
## Conflict detection
Something else may already control the store template you want to change:
Elementor Pro's Theme Builder, Divi's Theme Builder, or a block theme's own
WooCommerce templates. WPOS detects this and warns you, instead of making a
change that would never show on the store.
## What it looks like
In the video, WPOS restyles the product page of a small coffee store:
* A larger product gallery, four trust badges under **Add to cart**, and
related products in a four-column grid.
* Every style is namespaced, so the store restyle cannot leak into the rest of
the site.
* A "ships in 2 to 3 days" notice for one product category, added as a snippet
scoped to that category.
* WPOS points out, unprompted, that it invented the badge copy and that it had
not seen the page render yet, because the store was in coming-soon mode.
```text theme={null}
Restyle the single product page: a larger gallery, trust
badges under Add to cart, related products in 4 columns.
```
Before you publish, read any copy WPOS wrote for the store, such as delivery
or returns promises, and check the page in the preview. The agent flags what
it could not verify; the final check is yours.
How WPOS works in Gutenberg, Elementor, and Divi, with no lock-in.
Pin the brand so every page, including the store, stays on-brand.
# Accounts
Source: https://docs.wpos.ai/partners/accounts
Provision and manage customer accounts: create idempotently, set plans, grant credits, change status, and read connected sites.
A managed account is a customer account you provision and own. This guide covers
the full lifecycle. Every call needs the `provision` scope except the reads, which
need `read`.
## Provision an account
`POST /accounts` creates a customer account keyed by your own `externalRef`.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "owner@customer.com", "externalRef": "ws_123", "name": "Jordan Lee", "company": "Customer Co", "planId": "starter" }'
```
| Field | Required | Notes |
| - | - | - |
| `email` | yes | The account owner's email. |
| `externalRef` | yes | Your own id for this customer. Provisioning is idempotent on it. |
| `name` | no | Person or account name. |
| `company` | no | Company name. |
| `planId` | no | Defaults to your partner default plan. An unknown plan returns `plan_invalid`. |
**Idempotent by design.** Two calls with the same `externalRef` return the same
account. The first returns `201` with `existing: false`; a repeat returns `200`
with `existing: true` and ignores the other body fields. Build your provisioning
as fire-and-forget: retries never create duplicates.
The account is created with no password. Your customer never logs into WPOS
directly; they activate the plugin with the returned `licenseKey`, and you drive
everything else through the API or the [dashboard](/partners/dashboard).
If the email already belongs to an account that is not yours, the call returns
`409 email_conflict` and never attaches to it. Use an email your customer
controls, or one namespaced to your platform.
## Read an account
`GET /accounts/:accountId` returns the account with its credit balances and site
count.
```json theme={null}
{
"success": true,
"account": {
"id": "acc_...",
"email": "owner@customer.com",
"plan": "starter",
"status": "active",
"externalRef": "ws_123",
"credits": { "balance": 480, "usedTotal": 20, "addonBalance": 0 },
"siteCount": 1,
"createdAt": "2026-08-08T..."
}
}
```
## Change the plan
`POST /accounts/:accountId/plan` moves an account to a different plan.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts/acc_.../plan \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "planId": "pro" }'
```
## Grant credits
`POST /accounts/:accountId/credits` adds credits to an account. This is a grant
from your partner allocation, not a purchase: the customer gets no billing email.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts/acc_.../credits \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 500, "reason": "Monthly top-up" }'
```
`amount` is a positive integer. The response returns the new balance. Watch for
the [`credits.low` webhook](/partners/webhooks#events) to top accounts up before
they run dry.
## Suspend or reactivate
`POST /accounts/:accountId/status` sets the account's status. Suspending stops the
account from spending credits within one plugin validation cycle (about five
minutes); setting it back to `active` re-enables it.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts/acc_.../status \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "suspended" }'
```
`status` is one of `active`, `suspended`, or `cancelled`. Use this when a customer
churns or fails to pay you: it is your lever, not WPOS's.
## List an account's sites
`GET /accounts/:accountId/sites` returns the WordPress sites connected to the
account, including each site's white-label handoff state.
```json theme={null}
{
"success": true,
"sites": [
{
"id": "site_...",
"siteUrl": "https://customer.com",
"siteTitle": "Customer Co",
"pluginVersion": "2.3.0",
"isActive": true,
"lastSeenAt": "2026-08-08T...",
"handoff": { "mode": true, "agencyName": "Acme Agency", "logoUrl": "https://...", "primaryColor": "#1B3A6B", "showChat": true }
}
]
}
```
You do not create sites by API. A site appears here once your customer activates
the plugin on a real WordPress site with the account's license key. That
registration also fires a [`site.registered` webhook](/partners/webhooks#events).
## Ownership and errors
Every account and site must resolve to your partner. A request for one that is not
yours returns `404 not_found`, never `403`, so the API never leaks whether an id
exists. See the [error reference](/partners/api-reference#errors).
## Next
Run the agent on a connected site.
White-label the assistant on a site.
# API reference
Source: https://docs.wpos.ai/partners/api-reference
The Partner API in one place: base URL, authentication, scopes, rate limits, every endpoint, and the error envelope.
The Partner API is a stable HTTP API. Route shapes, payloads, and error codes are
frozen: we add to them, we do not break them.
## Base URL
```text theme={null}
https://api.wpcursor.com/api/partner/v1
```
All requests and responses are JSON.
## Authentication
Send your key on every request:
```text theme={null}
X-Partner-Key: wpk_...
```
A key is issued by WPOS with a fixed set of scopes. It is shown to you once and
stored only as a hash. Treat it like a password: keep it server-side, rotate it if
it leaks (contact WPOS to revoke and reissue), and never ship it to a browser. For
in-browser experiences, mint a narrow [embed session](/partners/embed) instead.
### Scopes
| Scope | Grants |
| - | - |
| `read` | Read accounts, sites, and job status. |
| `provision` | Create accounts, change plans, grant credits, set account status. |
| `handoff` | Set white-label branding on a site. |
| `jobs` | Submit, poll, and cancel build jobs. |
| `embed` | Mint embedded-assistant sessions. |
A call that needs a scope your key lacks returns `403 forbidden_scope`.
## Rate limits
Requests are limited per partner, at sixty per minute. Over the limit returns
`429 rate_limited`; back off and retry. Build jobs have their own capacity limits;
see [Build jobs](/partners/build-jobs#preconditions).
## Endpoints
| Method | Path | Scope | Purpose |
| - | - | - | - |
| `GET` | `/ping` | `read` | Confirm the key and see its scopes. |
| `POST` | `/accounts` | `provision` | [Provision an account](/partners/accounts#provision-an-account) (idempotent on `externalRef`). |
| `GET` | `/accounts/:id` | `read` | [Read an account](/partners/accounts#read-an-account). |
| `POST` | `/accounts/:id/plan` | `provision` | [Change the plan](/partners/accounts#change-the-plan). |
| `POST` | `/accounts/:id/credits` | `provision` | [Grant credits](/partners/accounts#grant-credits). |
| `POST` | `/accounts/:id/status` | `provision` | [Suspend or reactivate](/partners/accounts#suspend-or-reactivate). |
| `GET` | `/accounts/:id/sites` | `read` | [List connected sites](/partners/accounts#list-an-accounts-sites). |
| `POST` | `/sites/:siteId/handoff` | `handoff` | [Set white-label branding](/partners/branding#set-branding-on-a-site). |
| `POST` | `/sites/:siteId/jobs` | `jobs` | [Submit a build job](/partners/build-jobs#submit-a-job). |
| `GET` | `/jobs/:jobId` | `jobs` | [Poll a job](/partners/build-jobs#poll-for-the-result). |
| `POST` | `/jobs/:jobId/cancel` | `jobs` | [Cancel a job](/partners/build-jobs#cancel-a-job). |
| `POST` | `/sites/:siteId/embed-session` | `embed` | [Mint an embed session](/partners/embed#how-it-fits-together). |
## Errors
Every non-2xx response uses one envelope:
```json theme={null}
{ "error": { "code": "validation_error", "message": "email is required" } }
```
| Code | Status | Meaning |
| - | - | - |
| `unauthorized` | 401 | Missing, unknown, revoked, or expired key. |
| `forbidden_scope` | 403 | The key lacks the scope this call needs. |
| `partner_suspended` | 403 | Your partner account is not active. |
| `limit_reached` | 403 | Account cap hit, or insufficient credits for a job. |
| `not_found` | 404 | No such resource, or it is not yours. Used for cross-tenant access so ids never leak. |
| `validation_error` | 400 | A field is missing or malformed. |
| `plan_invalid` | 400 | Unknown `planId`. |
| `email_conflict` | 409 | The email already belongs to an account that is not yours. |
| `rate_limited` | 429 | Over the request or job-capacity limit. Retry after backing off. |
| `internal_error` | 500 | Something went wrong on our side. Retry; if it persists, contact us. |
Ownership failures answer `404 not_found`, never `403`. A request for an account,
site, or job that is not yours is indistinguishable from one that does not exist,
by design, so the API never reveals another partner's ids.
## Webhooks
Events are delivered and signed as described in [Webhooks](/partners/webhooks).
## Support
Something missing or behaving unexpectedly? Email
[partners@wpos.ai](mailto:partners@wpos.ai).
# Branding
Source: https://docs.wpos.ai/partners/branding
White-label the assistant your customers see: set the agency name, logo, and accent color per site, or a partner-wide default.
White-label branding, called handoff, replaces every WPOS mark your customer would
otherwise see with your own. It applies to the assistant inside the customer's
wp-admin and to the [embedded assistant](/partners/embed) in your product. Setting
it needs the `handoff` scope.
## Set branding on a site
`POST /sites/:siteId/handoff` turns handoff on for a site and sets its branding.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/sites/site_.../handoff \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{
"handoffMode": true,
"agencyName": "Acme Agency",
"logoUrl": "https://acme.com/logo.svg",
"primaryColor": "#1B3A6B",
"showChat": true
}'
```
| Field | Notes |
| - | - |
| `handoffMode` | Turn white-labeling on or off for the site. |
| `agencyName` | The name shown in place of WPOS. |
| `logoUrl` | Must be an `https://` URL. Shown in place of the WPOS wordmark. |
| `primaryColor` | A six-digit hex color, for example `#1B3A6B`, applied as the accent. |
| `showChat` | Whether the branded assistant is visible in the customer's wp-admin. Defaults to on for partner handoff. |
Omit a branding field with `handoffMode` on and it falls back to your
partner-wide default for that field. Set your defaults with WPOS so every site
you hand off is branded without you sending them each time.
Validation is strict: a `logoUrl` that is not https, or a `primaryColor` that is
not a six-digit hex, returns `validation_error`. WPOS persists the branding, then
pushes it to the live WordPress site. The response tells you both:
```json theme={null}
{ "success": true, "persisted": true, "pushed": true }
```
If the site could not be reached, `pushed` is `false` with a `pushError`, but the
branding is saved and syncs on the site's next check-in.
## What your customer sees
With handoff on, the assistant drops every WPOS and WPCursor name, wordmark, and
outbound link, and renders your agency name, logo, and accent color instead. Turn
`handoffMode` off and the site returns to the default appearance with no visual
trace of the change.
## Reading branding back
Branding comes back on each site in
[`GET /accounts/:accountId/sites`](/partners/accounts#list-an-accounts-sites)
under the `handoff` object, so you can show your customers their current
configuration.
## Next
The embedded assistant uses this same branding.
# Build jobs
Source: https://docs.wpos.ai/partners/build-jobs
Run the WPOS agent headlessly on a customer site: submit an instruction, poll for a structured result, and cancel when you need to.
A build job is one headless run of the WPOS agent against one site. You send a
plain-language instruction plus optional structured context, WPOS runs the agent
with no human in the loop, and you get back a structured result: a summary, the
changes it made, and any warnings. Every call needs the `jobs` scope.
## Submit a job
`POST /sites/:siteId/jobs`.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/sites/site_.../jobs \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{
"instructions": "Create an About page from the brief in context, publish it, and link it in the main menu.",
"context": { "brief": "Family-run bakery since 1998, three locations, known for sourdough." },
"externalRef": "job_about_001",
"model": "sonnet",
"timeoutMinutes": 15
}'
```
| Field | Required | Notes |
| - | - | - |
| `instructions` | yes | Plain-language task, up to 10,000 characters. |
| `context` | no | Structured data passed to the agent verbatim as JSON, up to 20,000 characters serialized. Briefs, approved copy, audit findings. |
| `externalRef` | no | Your own id. Makes the submission idempotent. |
| `model` | no | `sonnet` (default) or `opus`. |
| `timeoutMinutes` | no | 5 to 30, default 15. A run that exceeds it ends with status `timeout`. |
A new job returns `202`; a repeat of the same `externalRef` returns `200` with the
existing job.
```json theme={null}
{ "success": true, "job": { "id": "job_...", "status": "queued", "pollUrl": "/api/partner/v1/jobs/job_..." } }
```
Jobs run with no human in the loop. The agent never pauses to ask a question, so
put everything it needs in `instructions` and `context`. Structured context in
the `context` field beats stuffing data into the instruction prose.
## Preconditions
A job is accepted only when the site belongs to you and is active, the account is
active, and the account has credits. Insufficient credits returns `403
limit_reached`. There are caps on concurrency: a small number of partner jobs run
at once globally, and one running job per site. At capacity you get `429
rate_limited`; retry shortly.
## Poll for the result
`GET /jobs/:jobId`. Poll until `status` is terminal: `succeeded`, `failed`,
`timeout`, or `cancelled`.
```json theme={null}
{
"success": true,
"job": {
"id": "job_...",
"siteId": "site_...",
"externalRef": "job_about_001",
"status": "succeeded",
"model": "sonnet",
"result": {
"summary": "Created and published an About page and added it to the main menu.",
"changes": [
{ "type": "page_created", "pageId": 42, "url": "https://customer.com/about", "title": "About" }
],
"warnings": []
},
"creditsConsumed": 6,
"createdAt": "2026-08-08T...",
"startedAt": "2026-08-08T...",
"finishedAt": "2026-08-08T..."
}
}
```
### The result shape
On success, `result` is the agent's structured report:
* `summary`: one line describing what it did.
* `changes`: an array of `{ type, pageId?, url?, title? }`. `type` is one of
`page_created`, `page_updated`, `widget`, or `other`.
* `warnings`: anything the agent flagged but did not treat as failure.
If the agent finished but its final report could not be parsed, `result` is
`{ "raw": "..." }` with the raw text, and `status` is still `succeeded`.
Rather than polling, register a webhook and handle the
[`job.completed` event](/partners/webhooks#events), which fires on every terminal
status and carries the same result. Use polling for interactive flows, webhooks
for everything else.
## Cancel a job
`POST /jobs/:jobId/cancel` is best-effort: a queued job becomes `cancelled`, a
running job is stopped and becomes `cancelled`, and a job already finished returns
`200` with its current status unchanged.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/jobs/job_.../cancel \
-H "X-Partner-Key: $WPOS_PARTNER_KEY"
```
## Credits and cost
After a job finishes, WPOS converts its cost to credits and consumes them from the
account, recording both `creditsConsumed` and the underlying `cost_usd` on the
job. Any run that actually started consumes at least one credit. Watch spend per
account in the [dashboard](/partners/dashboard).
## Next
The same agent, driven by your customers in your UI.
Get told when a job finishes.
# Concepts
Source: https://docs.wpos.ai/partners/concepts
The handful of ideas the Partner Program is built on: partners, managed accounts, sites, credits, build jobs, scopes, and webhooks.
A short glossary. Everything else in this section assumes these.
## Partner
Your organization, as WPOS sees it. A partner has a name, a slug, default
branding, an optional account cap, a default plan for the accounts you create,
and an optional webhook endpoint. Every account, key, and build job belongs to
exactly one partner.
## Managed account
A customer account you provision and own. It is a normal WPOS account with one
difference: it is marked partner-managed, so it carries no WPOS-facing billing
and the WPOS checkout surfaces are closed to it. You reference each account by
your own `externalRef` (for example your workspace or customer id), and WPOS
keeps provisioning idempotent on the pair of your partner id and that ref.
## Site
A WordPress site connected to one of your managed accounts through the WPOS
plugin. Sites are where the agent actually works. You do not create sites by API;
they appear once the plugin on a real WordPress site registers against an account
you provisioned.
## Credits
The unit WPOS meters work in. A build job consumes credits based on its cost. You
hold your customers' credits: grant them to an account, watch the balance, and get
a `credits.low` webhook before an account runs dry. Because accounts are
partner-managed, credits are yours to price and resell.
## Build job
One headless run of the WPOS agent against one site. You submit a plain-language
instruction plus optional structured context, WPOS runs the agent with no human in
the loop, and you poll for a structured result (a summary, the changes it made,
and any warnings). See [Build jobs](/partners/build-jobs).
## The embedded assistant
The WPOS agent, running inside your own product in an iframe, branded as yours. It
comes in two roles: `manager` for your operators and `client` for your end
customers. The browser never holds your API key; each session is a short-lived,
site-scoped token you mint. See [Embed the assistant](/partners/embed).
## API key and scopes
You authenticate to the Partner API with an `X-Partner-Key` header. Each key
carries a set of scopes that gate what it can do:
| Scope | Grants |
| - | - |
| `read` | Read accounts, sites, and job status. |
| `provision` | Create accounts, change plans, grant credits, set account status. |
| `handoff` | Set white-label branding on a site. |
| `jobs` | Submit, poll, and cancel build jobs. |
| `embed` | Mint embedded-assistant sessions. |
We issue keys with the scopes your integration needs. Treat a key like a
password: it is shown once, stored only as a hash, and is revocable. See
[Authentication](/partners/api-reference#authentication).
## Webhook
An HTTP callback WPOS sends to your endpoint when something happens: a site
connected, credits crossed the low threshold, a build job finished. Every
delivery is signed so you can verify it came from WPOS. See
[Webhooks](/partners/webhooks).
## Next
Put these together: provision an account and run a build job.
# Partner Dashboard
Source: https://docs.wpos.ai/partners/dashboard
The self-serve control panel for your team: manage customer accounts, watch build jobs, and configure your integration without writing code.
The Partner Dashboard is where your team runs the partner program by hand: create
and manage customer accounts, watch build jobs and their results, track usage, and
configure your webhook. It is the no-code counterpart to the
[Partner API](/partners/api-reference), backed by the same data.
## Getting in
There are two ways to open the dashboard, and they land in the same place.
Once WPOS enables your account as a partner, a **Partner** item appears in your
WPOS dashboard at [app.wpos.ai](https://app.wpos.ai). Open it and click
**Open Partner Dashboard**. You are signed straight in, no key to paste.
You can also sign in directly at
[api.wpcursor.com/partner/portal](https://api.wpcursor.com/partner/portal) with
a partner API key. The key is exchanged for a short session and is never stored
in the browser. What you can do follows the key's
[scopes](/partners/concepts#api-key-and-scopes): a read-only key gives a
read-only dashboard.
Signing in from your WPOS account gives you the full dashboard. Signing in with a
key gives you exactly what that key's scopes allow, which is useful for handing a
read-only view to someone without provisioning rights.
## What you can do
### Overview
Credits used, build jobs and their success rate, job spend, and webhook delivery
health across your whole book, with a 7, 30, or 90 day window. Below the tiles are
per-day charts for credits and job outcomes, your top accounts by usage, and
cumulative growth.
### Accounts
Every customer account you manage, with plan, status, credit balance, and site
count. From here you can create an account, suspend or reactivate one, grant
credits, and drill into a customer's connected sites. See
[Accounts](/partners/accounts) for what each action means.
### Build jobs
The full history of agent runs on your customers' sites. Click any job to see the
instruction you sent, the structured result, and any error. Queued or running jobs
can be cancelled. See [Build jobs](/partners/build-jobs).
### Settings
Configure your webhook endpoint yourself: set or change the URL, and WPOS rotates
the signing secret and shows it to you exactly once. Below it is a live log of
recent deliveries with the failure reason on any that did not land, so you can
debug an endpoint without leaving the page. You can also see your API keys
(metadata only, never the secret) and your integration details.
## Roles and access
The dashboard respects your key's scopes on every action, so a read-only operator
can watch usage and jobs without being able to change anything. Mutations are
recorded in your partner audit log with who did them.
## Next
Provision and manage customer accounts.
Configure and verify lifecycle events.
# Embed the assistant
Source: https://docs.wpos.ai/partners/embed
Put the WPOS agent inside your own product as a branded iframe, for both your operators and your end customers, without exposing your API key.
The embedded assistant is the WPOS agent running inside your product, in an
iframe, branded as yours. Your operators drive it as a `manager`, and your end
customers drive it as a `client`. The browser never holds your API key: you mint a
short-lived, site-scoped session token on your server and load it in the frame.
This needs the `embed` scope.
## How it fits together
Call `POST /sites/:siteId/embed-session` with your partner key. You get a
one-hour token and a ready-to-frame URL. The site must belong to you.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/sites/site_.../embed-session \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "role": "client", "displayName": "Jordan" }'
```
```json theme={null}
{
"success": true,
"role": "client",
"token": "...",
"expiresIn": 3600,
"url": "https://api.wpcursor.com/embed/chat?token=..."
}
```
Drop the returned `url` into an iframe in your app.
```html theme={null}
```
The embed surface sets a `frame-ancestors` policy that allows your configured
origins, so it loads inside your app and nowhere else.
The assistant loads with your branding and runs the agent on the token's site.
Each message is a build job under the hood, bounded by the same credits and
caps as the [Build jobs](/partners/build-jobs) API. When the token expires
after an hour, mint another.
## Roles
| Role | For | Framing |
| - | - | - |
| `manager` | Your operators | Full agent access, presented as an operator tool. |
| `client` | Your end customers | The same execution, presented as "request a change". |
The role is stamped into the session token and travels with every job the session
runs, so your reporting can tell operator work from customer work.
## Branding
The embedded assistant renders with the site's white-label branding: agency name,
logo, and accent color, falling back to your partner defaults. Set them per site
through [Branding](/partners/branding), or set partner-wide defaults with WPOS. No
WPOS name appears to your customer.
## Security model
Mint session tokens on your server, never in the browser, and never ship your
partner key to the client. The embed token is deliberately narrow: it is
site-scoped, expires in an hour, and only works on the embed surface. It cannot
read your other sites or your account.
* The token is an embed-only credential. It cannot be used as a partner key or a
site token.
* Everything the frame can do is scoped to the one site the token was minted for.
* Configure which origins may frame the assistant with WPOS when you set up your
embed integration.
## Next
Set the name, logo, and color your customers see.
The API the embedded assistant runs on.
# Partner Program
Source: https://docs.wpos.ai/partners/overview
Resell WPOS under your own brand: provision customer accounts, run the agent on their sites, and embed a white-label assistant in your own product.
The Partner Program lets an agency or platform operate WPOS on behalf of its own
customers, under its own brand. You provision customer accounts by API, run the
WPOS agent headlessly on their sites, embed a branded assistant inside your app,
and settle credits on your own terms. Your customers see your name, not ours.
This section is for partners building on WPOS. If you operate your own sites,
start with the [Workspace](/agencies/workspace) instead. The Partner Program is
the layer above that: you operate WPOS for other people.
## Two ways to work
Every partner gets both, and most use both.
A self-serve control panel for your team: create and manage customer
accounts, watch build jobs, and configure webhooks. No code required.
A frozen HTTP API for provisioning accounts, running build jobs, and
embedding the assistant. This is how you wire WPOS into your own product.
## What you can do
* **Provision customer accounts** on demand, each tied to your own reference id,
with no passwords and no WPOS-branded billing. See [Accounts](/partners/accounts).
* **Run the agent on a customer site** with a structured instruction, then poll
for the result. See [Build jobs](/partners/build-jobs).
* **Embed the assistant** inside your product for your operators and your end
customers, fully branded. See [Embed the assistant](/partners/embed).
* **White-label every surface** your customers touch: the in-site assistant, the
embedded chat, and the sites you hand off. See [Branding](/partners/branding).
* **Receive lifecycle events** as signed webhooks: a site connected, credits
running low, a build job finished. See [Webhooks](/partners/webhooks).
## How billing works
Customer accounts you provision are marked partner-managed. They carry no
WPOS-facing checkout: the customer never sees a WPOS bill, and the WPOS Stripe
surfaces are closed to them at the server. You hold the credits, you grant them
to accounts as you see fit, and you bill your customer however you like.
## Getting access
Partner access is enabled by WPOS, not self-serve, so we can set your plan terms,
account limits, and default branding with you. Once your account is enabled as a
partner, the [Partner Dashboard](/partners/dashboard) unlocks in your WPOS account
automatically, and we issue the API key you integrate against.
Email [partners@wpos.ai](mailto:partners@wpos.ai) and we will set you up.
## Next
The handful of ideas the rest of this section builds on.
Provision an account and run your first build job.
# Quickstart
Source: https://docs.wpos.ai/partners/quickstart
Provision a customer account, connect a site, and run your first build job with the Partner API.
This takes you from an API key to a finished build job. It assumes WPOS has
enabled your account as a partner and issued you a key. If not, see
[Getting access](/partners/overview#getting-access).
The base URL for the Partner API is `https://api.wpcursor.com/api/partner/v1`.
Authenticate every request with the `X-Partner-Key` header.
Confirm the key works and see the scopes it carries.
```bash theme={null}
curl https://api.wpcursor.com/api/partner/v1/ping \
-H "X-Partner-Key: $WPOS_PARTNER_KEY"
```
```json theme={null}
{
"success": true,
"partner": { "id": "ptr_...", "name": "Acme Agency", "slug": "acme" },
"scopes": ["provision", "read", "handoff", "jobs", "embed"]
}
```
Create an account for a customer, keyed by your own reference. This call is
idempotent on `externalRef`: send it again and you get the same account back
with `existing: true`, never a duplicate.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "owner@customer.com", "externalRef": "ws_123", "company": "Customer Co" }'
```
```json theme={null}
{
"success": true,
"existing": false,
"account": { "id": "acc_...", "email": "owner@customer.com", "licenseKey": "WPC-...", "plan": "starter", "createdAt": "2026-08-08T..." }
}
```
Keep the `licenseKey`: your customer uses it to activate the WPOS plugin on
their site.
Your customer installs the WPOS plugin on their WordPress site and activates
it with the license key from the previous step. See
[Install the plugin](/get-started/install). Once the plugin registers, the
site appears on the account and you get a
[`site.registered` webhook](/partners/webhooks#events).
Read the account's sites to get the `siteId` you will run jobs against:
```bash theme={null}
curl https://api.wpcursor.com/api/partner/v1/accounts/acc_.../sites \
-H "X-Partner-Key: $WPOS_PARTNER_KEY"
```
Build jobs consume credits. Grant the account a balance to work from.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/accounts/acc_.../credits \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 500, "reason": "Onboarding grant" }'
```
Submit a plain-language instruction for the agent to run on the site. The call
returns immediately with a job you poll.
```bash theme={null}
curl -X POST https://api.wpcursor.com/api/partner/v1/sites/site_.../jobs \
-H "X-Partner-Key: $WPOS_PARTNER_KEY" \
-H "Content-Type: application/json" \
-d '{ "instructions": "Add a testimonials section to the homepage with three quotes.", "externalRef": "job_001" }'
```
```json theme={null}
{ "success": true, "job": { "id": "job_...", "status": "queued", "pollUrl": "/api/partner/v1/jobs/job_..." } }
```
Poll the job until it reaches a terminal status. A finished job carries a
structured summary of what changed.
```bash theme={null}
curl https://api.wpcursor.com/api/partner/v1/jobs/job_... \
-H "X-Partner-Key: $WPOS_PARTNER_KEY"
```
```json theme={null}
{
"success": true,
"job": {
"id": "job_...",
"status": "succeeded",
"result": {
"summary": "Added a testimonials section with three quotes to the homepage.",
"changes": [{ "type": "page_updated", "pageId": 12, "url": "https://customer.com/", "title": "Home" }],
"warnings": []
},
"creditsConsumed": 4
}
}
```
Instead of polling, you can register a webhook and receive a
[`job.completed` event](/partners/webhooks#events) the moment it finishes.
Prefer webhooks to tight polling loops. Poll at a human pace (every few seconds)
for interactive flows, and lean on the `job.completed` webhook for everything
else.
## Next
Plans, credits, status, and sites in depth.
Instructions, context, results, and caps.
Put the agent inside your own product.
Every endpoint, error, and limit.
# Webhooks
Source: https://docs.wpos.ai/partners/webhooks
Receive signed lifecycle events from WPOS: a site connected, credits running low, a build job finished. Verify, handle, and debug them.
Webhooks push events to your endpoint the moment they happen, so you do not have to
poll. WPOS signs every delivery so you can prove it came from us. You set one
endpoint per partner, either in the [dashboard](/partners/dashboard) or with WPOS.
## Set your endpoint
In the Partner Dashboard, open **Settings**, then set your webhook URL. WPOS
generates a signing secret and shows it to you **exactly once**. Store it now: it
is never shown again, and changing the URL rotates it. The endpoint must be an
`https://` URL.
## Events
A WordPress site connected to one of your managed accounts.
```json theme={null}
{
"event": "site.registered",
"timestamp": "2026-08-08T...",
"data": { "accountId": "acc_...", "externalRef": "ws_123", "siteId": "site_...", "siteUrl": "https://customer.com", "siteTitle": "Customer Co", "pluginVersion": "2.3.0" }
}
```
An account's balance crossed below ten percent of its plan's monthly credits.
Fires once per billing cycle, so you can top the account up in time.
```json theme={null}
{
"event": "credits.low",
"timestamp": "2026-08-08T...",
"data": { "accountId": "acc_...", "externalRef": "ws_123", "balance": 45, "monthlyCredits": 500 }
}
```
A build job reached a terminal status. Fires on `succeeded`, `failed`,
`timeout`, and `cancelled`, and carries the same result as the job poll.
```json theme={null}
{
"event": "job.completed",
"timestamp": "2026-08-08T...",
"data": { "jobId": "job_...", "externalRef": "job_001", "siteId": "site_...", "accountId": "acc_...", "status": "succeeded", "result": { "summary": "...", "changes": [], "warnings": [] }, "creditsConsumed": 6 }
}
```
## The request
Every delivery is a `POST` to your URL with these headers:
| Header | Value |
| - | - |
| `Content-Type` | `application/json` |
| `X-WPOS-Event` | The event name, for example `job.completed`. |
| `X-WPOS-Signature` | `sha256=`, an HMAC-SHA256 of the raw request body keyed with your webhook secret. |
The body is `{ "event": "...", "timestamp": "...", "data": { ... } }`.
## Verify the signature
Always verify before you trust a delivery. Compute the HMAC over the **raw** body
and compare it, in constant time, to the header.
```javascript theme={null}
import crypto from 'crypto';
// req.rawBody is the exact bytes WPOS sent. Do not re-serialize the parsed JSON.
function verifyWposWebhook(rawBody, signatureHeader, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
const a = Buffer.from(signatureHeader || '', 'utf8');
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```
Verify against the raw request body, not a re-serialized copy of the parsed JSON.
If your framework parses the body before you see it, capture the raw bytes (for
example Express's `express.json({ verify })` hook) so the HMAC matches.
## Retries and delivery
WPOS expects a `2xx` promptly. Deliveries time out after eight seconds. On any
failure or non-2xx, WPOS retries twice: about one minute later, then about five
minutes after that. After the third attempt the delivery is marked failed. Make
your handler idempotent: identify the work by `jobId` or `siteId` and tolerate a
repeat.
## Debug deliveries
The **Settings** page in the [dashboard](/partners/dashboard) shows a live log of
recent deliveries with the event, attempt count, outcome, and the failure reason
on any that did not land. Use it to confirm your endpoint is reachable and
returning 2xx.
## Next
Every endpoint, scope, error, and limit in one place.
# Changelog
Source: https://docs.wpos.ai/resources/changelog
What changed in WPOS, release by release.
Product updates that reached every connected site, newest first. Version numbers
refer to the WPOS plugin. Sites receive releases automatically through the
built-in update system, usually within seconds of publish, so the version below
is the version your fleet runs. Entries marked **app.wpos.ai** are updates to
the [Workspace](/agencies/workspace), which reach every account the moment they
ship.
* **Connect your own AI agent to the site.** A new **AI Agents** tab in
Settings lets an outside assistant work on your site through WPOS: it
signs in with your WordPress account and gets the same tools the agent
uses. The tab is hidden on a site in [handoff](/agencies/handoff).
* **WPOS uses what your plugins expose.** Plugins that register WordPress
abilities are now driven directly, rather than worked around, including
Elementor 4.3's own editor abilities on a site that runs it. Every action
still runs as the WordPress user who asked for it.
* **Attach files while a task is running.** Files no longer wait for the run
to finish. Chats also name themselves as they go, and renaming one is a
single step. See [Tasks](/operate/tasks).
* **Clearer tool results.** What the agent did appears as a status line with
screenshot previews inline, and the context meter moves during the run
rather than only at the end.
* **A first look at a new site.** The first chat after activating a licence
scans the site, so the agent starts knowing what is there.
* **Migration is harder to trigger by accident.** Migrating asks for
confirmation first, reports failures in the app, and now handles SQLite
sites. See [Migration key](/agencies/migration-key).
* **Every action checks its WordPress capability.** Command Center routes
verify what the person is allowed to do in WordPress before acting, and
agency managers and operators can now press **Update now**.
* **Analytics.** A new Analytics section shows credits, sites, and team
activity over time, with a detail view for each site and each member.
* **Access requests.** When a member meets a control they are not allowed to
use, they can press **Request access**. Owners, and Agency Managers for
their own team, approve or decline. See [Members](/agencies/members).
* **Permissions at invite time.** Choose what a new member may do before they
join, and see straight away when an address cannot be invited. See
[Invite members](/agencies/invite-members).
* **Sandboxes are permanent.** Every sandbox now stays until you delete it.
Name a sandbox when you create it, with a live availability check, give it
a readable link later, and let the launch carry on in the background. See
[Sandboxes](/agencies/sandboxes).
* **A tour of the Workspace.** New accounts answer a few short setup
questions, then get a Quick or Full tour of the dashboard, which you can
replay from Settings. See [Onboarding](/get-started/onboarding).
* **My Plan for managers.** The owner can let an Agency Manager see the My
Plan page, without the billing controls.
* **Pick up to 5 elements at once.** The pin on a selected element is now
the comment box itself, and picks are numbered, so one message can carry
several fixes. See [Select and edit](/operate/select-and-edit).
* **Move a whole site with one key.** A migration key now transfers the
site itself, with live progress while it runs, and the key can be pasted
straight into the licence field. See
[Migration key](/agencies/migration-key).
* **Reuse an earlier chat.** Drag a task from the list onto the chat box to
hand its history to the agent. See [Tasks](/operate/tasks).
* **The agent checks work before you publish it.** Preview access lets it
look at an unpublished page, so a draft is verified before it goes live.
* **Work is attributed to the person.** A request now identifies the member
behind it, not only the licence key it used. See
[Members](/agencies/members).
* PDF work is named in the activity panel instead of running unlabelled.
* **WordPress logins for members.** Assigning a site now gives the member
their own WordPress login on it: hand over an existing account or create a
new one. See [Assign sites](/agencies/assign-sites).
* **Credit budgets.** Give each member a credit budget, then give more or
take unspent credits back. See [Members](/agencies/members).
* **Connectors, one site at a time.** Connecting a tool in the Workspace now
asks which site it is for and switches it on only there. See
[Connectors](/connect/connectors).
* **Partner dashboard.** Partner Program members get Accounts, Build jobs,
Webhook, and API keys pages in the Workspace, and can move a site between
their managed accounts. See [Partners](/partners/overview).
* **More than 100 sites.** Agencies that need more than 100 site licences get
a link to book a call with the WPOS team straight from the plan page.
* **Running low on credits.** When a Free account is nearly out of credits,
the dashboard offers an upgrade or a conversation with the WPOS team.
* **Members.** Bring your team into the Workspace as Agency Managers and
Operators, each with their own login. See [Members](/agencies/members).
* **Invites.** Invite people by email or with an invite link. See
[Invite members](/agencies/invite-members).
* **Assign sites.** Members see only the client sites you assign them. See
[Assign sites](/agencies/assign-sites).
* **Credit requests.** A member who runs low can ask for more credits, and
whoever funds them approves or declines.
* **Migration keys.** Each site has a migration key that moves it, with its
chats, connectors, and settings, to another WordPress install. See
[Sandboxes](/agencies/sandboxes).
* **Free plan.** New accounts start on a Free plan, with no payment step at
sign-up.
* **Choose your site licences.** On the Agency plan, pick exactly how many
site licences you need, from 1 to 100, when you subscribe or change plan.
See [Billing](/agencies/billing).
* **Custom domains for sandboxes.** Serve a permanent sandbox on a domain you
own. WPOS handles the certificate and sends email updates as the domain
goes live.
* **Choose plugins at launch.** Pick the plugins a new sandbox starts with.
See [Sandboxes](/agencies/sandboxes).
* **One-click WooCommerce connect.** Connect a store from the Connectors row
in the plugin, or for a site from the Workspace, without creating REST
keys by hand. See [WooCommerce](/connectors/woocommerce).
* **Read the site's own source.** The Files panel browses the theme and
plugin files behind the page, not just the media library. See
[Files](/operate/files).
* **Handoff hardening.** A handed-over site asks for the agency name,
carries its state across open pages, and gates what the client can reach.
See [Client handoff](/agencies/handoff).
* **Snippets keep a record.** A snippet run once leaves an audit log entry.
* Fixes: a live connector is no longer reported as failed, a licence
survives a backend outage instead of detaching, blank chat replies are
gone, and Gutenberg container blocks emit their wrapper element again.
* Accounts enrolled in the Partner Program get a **Partner** section in the
Workspace. See [Partner Program](/partners/overview).
* A consolidation release: every improvement proven on the development
channel through July reaches the public build in one update.
* The approval system's route matrix covers more site actions, so more of
what the agent does is classified before it runs.
* Chat message parsing is rebuilt as a tested module, making replies more
resilient to unusual model output.
* A dedicated security release. Requests to the site connection API can no
longer be replayed, outbound requests pin their DNS resolution so they
cannot be redirected mid flight, and diagnostics output now redacts
secrets.
* The code validator rejects generated code that writes files.
* Snapshot handling for Elementor edits is hardened.
* Fixed a crash in the license domain check that could surface on sites
with unusual domain configurations.
* The Tasks panel reflects task progress live instead of waiting for a
refresh, plus smaller chat and tasks interface cleanups.
* [Jotform](/connectors/jotform) is available as a first-party connector.
* Connector notifications now read "connected" rather than "installed",
matching what actually happened.
* The Frontier (Fable 5) model option added in v2.2.0 is withdrawn from
the model selector.
* Maintenance release: the chat interface assets are rebuilt and reshipped.
* The model selector adds the Frontier (Fable 5) options for the site agent.
* Long question-and-answer recaps in chat now collapse, so a working thread
stays readable.
* Replies to support tickets now route into the conversation they belong to.
* Document viewer toolbar cleanup: clearer icons and consistent colors for
open document tabs.
* Support panel overhaul: report a bug without leaving the
[Command Center](/operate/command-center), with a built-in screen recorder
that attaches exactly what you saw to the ticket.
* The [Skills](/memory/skills) catalog caches persistently, so it opens
instantly on return visits.
* Security hardening on the site connection API.
* The Command Center recognizes the Standard and Agency plans.
* PDF preview in chat, a credits panel toggle, and cleaner response
formatting.
* [Site tree](/operate/site-tree) gains a widget-scoped code view and a
code search that works the way an editor's does.
* This documentation site launches at
[docs.wpos.ai](https://docs.wpos.ai).
* WPCursor becomes WPOS. Every user-facing string in the product is renamed.
* New Geist type and smooth follow-scroll in chat.
* The [Workspace](/agencies/workspace) moves to
[app.wpos.ai](https://app.wpos.ai). Older addresses redirect.
* The live browser preview recovers cleanly from expired sessions instead of
going dark.
* WooCommerce template overrides: the site agent can customize WooCommerce
templates safely, with a conflict detector that flags themes already
overriding the same template.
* Wireframe Kit styles ship with the plugin for faster layout drafts.
* The [Connectors](/connect/connectors) panel is redesigned, with one search
across the whole catalog.
* Licenses now allow staging, preview, and development domains alongside the
licensed production domain.
* Domain checks self-heal after host migrations and site clones, instead of
blocking with a mismatch error.
* A proper WPOS icon in the WordPress admin menu, task spinners, and chat
notification banners.
* The [Skills](/memory/skills) panel arrives: read, edit, and extend the
Skills your site agent uses, from inside the Command Center.
* New Skills catalog with per-skill icons.
* The slim left sidebar returns, and files attached in chat get preview
cards.
* Bring your own credentials for [Connectors](/connect/connectors): guided
setup for API key, bearer token, OAuth2, and Google service account
authentication.
* Connector sign-in popups reload cleanly mid-session.
* Divi 4 support through the Builder Bridge: WPOS widgets render natively in
the Divi Visual Builder, with no compatibility warning.
* Right panel redesign: desktop, tablet, and mobile viewport toggle on the
live preview, and a page picker in the preview header.
* Support tickets: submit a bug report from the Command Center and track its
status. (v2.1.158)
* Conversations name themselves after the first reply, history loads up to
500 messages, and context survives a page refresh. (v2.1.148 to v2.1.163)
* An Expert 1M model option for long jobs, and a chime plus browser
notification when a task finishes. (v2.1.151, v2.1.156)
* License onboarding screen for new installs. (v2.1.162)
* Chat modes renamed to Quick and Balanced, and widget and snippet
management moved from the WordPress admin sidebar into the Command
Center. (v2.1.166, v2.1.167)
* A Full Width page template that works without Elementor Canvas. (v2.1.147)
* Manage WordPress users from chat: list, create, update, delete, and change
roles. (v2.1.28)
* Pick your model directly; the selector no longer routes the choice for
you. (v2.1.66)
* Handoff mode can be switched on and off remotely from the Workspace, no
password required at the site. (v2.1.82)
* Full-screen file preview in the workspace file browser, and media uploads
from Google Drive links. (v2.1.23, v2.1.26)
* Automatic update delivery: connected sites receive new releases within
seconds of publish. (v2.0.36 to v2.1.3)
* Staged release channels, so changes prove themselves before reaching every
site. (v2.1.19)
* One admin menu, with chat as the default screen. (v2.1.0)
* Workspace file browser: browse files and attach them to chat
messages. (v2.1.20)
* A compatibility wave for WP Engine and PHP 7.4 hosts, and correct
Elementor layout detection on both container and legacy sites. (v2.1.10
to v2.1.18)
* Security hardening: constant-time license verification and SHA-256
package integrity checks on every update. (v2.0.38, v2.0.39)
Releases v2.0.0 through v2.0.35, including domain-locked licensing in
v2.0.3, predate automatic update delivery and this changelog.
# FAQ and troubleshooting
Source: https://docs.wpos.ai/resources/faq
Answers to the questions agencies actually ask, and fixes for the issues support actually sees.
Every answer here comes from a real support case. If yours is not covered,
report it from the Support panel in the
[Command Center](/operate/command-center): it attaches diagnostics and a
screen recording to the ticket, which is the fastest route to a fix.
## Chat and responses
### The chat timed out or stopped responding. Is my work lost?
Usually not. On heavy jobs (a full page build, copying a page, a large
restyle) the agent often keeps working on the site even after the chat view
reports a timeout. Before re-running the request, check the page or the Tasks
panel first: re-running a job that actually finished does the work twice and
meters twice. If the thread itself will not respond again, start a new
conversation. Changes already applied to the site are saved on the site.
### "We couldn't resume this conversation."
The session behind that thread expired or returned empty. Start a new
conversation and continue from there. Nothing already applied to the site is
affected.
### "This conversation has reached its context limit."
Long threads fill the model's working context, and a thread that hits the
limit cannot continue. Start a new conversation and state the goal fresh. The
agent reads the current state of the site when it starts, so a new thread
picks up from where the site is now, not from zero.
### The Command Center is stuck on "Almost ready" and never replies.
Reload wp-admin and reopen the Command Center. If it still will not
initialize, report it from the Support panel so the diagnostics reach the
team with the ticket.
## Credits
### I was charged credits but the request failed.
Credits meter the agent's work while it runs, so a run that errors out or
times out still shows usage. If a run failed through no fault of the request,
report it from the Support panel with that conversation open so the team can
review the run itself. For big jobs, smaller scoped requests (one page, one
section) fail less and cost less per attempt than one broad instruction.
## Connecting sites
### Writes to the REST API return 403, but reads work.
A firewall or bot protection layer (often Cloudflare or a security plugin) is
challenging POST and PUT requests to `/wp-json/` while letting GET requests
through. Allowlist WPOS traffic in that layer, or exempt authenticated REST
API writes from the challenge. If you need the current addresses to
allowlist, ask through the Support panel.
### My license key will not validate on another site.
Licenses are locked to a site's domain, and each connected site uses one
license slot from your plan; add more from [Billing](/agencies/billing). The
www and http/https variants of a domain count as the same site, but a
different subdomain or a second domain is its own site. If the key belongs to
that site and still refuses, wait a few minutes and try again: license
results are cached briefly, so a just-fixed license can take a few minutes to
start validating. Still stuck? Contact support with the domain exactly as it
appears in the browser.
## Connectors
### A connector will not connect, or the sign-in popup fails.
First make sure the plugin is current: connectors added recently do not exist
in older builds, which surfaces as an "unknown connector" error. Sites update
automatically within seconds of a release, so a reload is normally enough.
Then retry from the [Connectors](/connect/connectors) panel with popup
blocking off for your wp-admin domain. If it still fails, the connector's own
page lists its connect flow step by step.
## Files
### Reading a PDF freezes the chat.
Some large or unusually built PDFs stall the viewer. Splitting the document
or attaching the relevant pages as a smaller file gets past it. If a
particular PDF freezes repeatedly, attach it to a Support panel ticket so the
team can reproduce it exactly.
# Get help
Source: https://docs.wpos.ai/resources/get-help
Report a bug, request a feature, or ask a question from inside the Command Center, with a built-in screen recorder.
Something broken, missing, or unclear? Report it without leaving the
[Command Center](/operate/command-center): open a new tab in the right panel
and choose **Report a bug**. You can also raise one from **Support** in the
[Workspace](/agencies/workspace), where your tickets are listed as All, Open,
and Resolved, and each one is a thread the team replies in. For answers to the
most common issues first, see the [FAQ](/resources/faq).
## Filing a report
1. Pick the type: **Bug**, **Feature request**, or **Question**.
2. Say what went wrong and what you expected. Specific beats long: the page
you were on, the action you took, what happened instead.
3. Set the severity in plain terms: **I can't use WPOS**, **I can work
around it**, or **Minor annoyance**. This is how the team triages.
4. Optionally hit **Record Screen** and reproduce the problem. The recording
attaches exactly what you saw to the ticket, which usually saves a round
of back and forth.
5. Send it. Browser info, plugin version, and console logs are captured
automatically, so you do not need to collect any of that yourself.
## What happens next
Your report becomes a support ticket with the WPOS team. Replies land back
in the product, in the conversation the ticket belongs to, so you do not
need to watch an email thread. If a resolved ticket turns out not to be
fixed, replying to it reopens it.
For anything involving a failed or overcharged run, report it with that
conversation open. The ticket then carries the run context the team needs
to review it.
# Sitemaps, robots, and llms.txt
Source: https://docs.wpos.ai/seo/crawlers
What WPOS serves to search crawlers and AI agents automatically: sitemap, robots.txt, and an llms.txt for AI readers.
Search engines and AI agents read a site through a few well-known files. WPOS
generates and maintains all of them, so a connected site is crawlable and
AI-readable from day one without configuration.
## llms.txt
WPOS auto-generates `/llms.txt`, the emerging standard that gives AI agents a
clean, markdown summary of your site: name, tagline, language, and an index
of the content that matters. It updates as the site changes, so an AI reading
your client's site always sees current information.
Check yours at `yourdomain.com/llms.txt`.
## Sitemap
The site serves a full XML sitemap at `/wp-sitemap.xml`, built on the
WordPress sitemap system and maintained automatically as content is added,
updated, or removed. The sitemap location is also advertised in robots.txt,
so crawlers find it without manual submission.
## robots.txt
WPOS manages `/robots.txt`. The default output keeps admin and commerce
plumbing out of the index and points crawlers at the sitemap. Rules can be
extended per site, including rules aimed at specific AI crawlers, by asking
the agent in [chat](/operate/chat).
## Redirects
When URLs change, WPOS serves proper 301 redirects so links and rankings
survive. The agent creates redirects as part of restructuring work rather
than leaving old URLs to 404.
After a migration or a big restructure, ask the agent in
[chat](/operate/chat) to check for broken links and missing redirects. It
works from the site's own link and redirect data.
# API card
Source: https://docs.wpos.ai/ultra/api-card
The condensed one-page authoring reference: intrinsics, sx keys, kit helpers, and the gotcha library.
This is the complete authoring surface of elementor-jsx on one page. Every name
here is real and tested. It is also available offline, always current with your
installed version, via:
```bash theme={null}
npx exjsx api
```
## fs-project layout
```text theme={null}
site/
site.config.mjs export default { name: 'mysite' }
theme.mjs export default defineTheme({ mode:'literal', colors:{...}, fonts:{...} })
components/*.jsx shared components (import-free: the prelude provides the API as free vars)
pages/home.page.jsx export const meta = { title, slug?, seo?, template? }
export default ({ theme }) =>
```
Build and deploy: `exjsx build `, then `exjsx deploy `
(idempotent, self-primes CSS, detects the Elementor version over REST, no
wp-cli needed). `exjsx lint ` before every deploy. Details:
[Projects](/ultra/projects), [Deploy](/ultra/deploy).
## Intrinsics: the only tags
| Tag | Notes |
| - | - |
| `box` `div` `col` `row` `section` | Flex containers. `row` forces dir row; `section` renders `` |
| `h1` `h2` `h3` `h4` / `heading` | Text props apply; inline ``, ``, `
` children OK |
| `text` `p` | Paragraph; `href` renders a real anchor (use for links and buttons-as-links) |
| `img` | `src` = URL string (inline `alt` OK) or attachment id (alt comes from the media library) |
| `html` | Raw HTML/SVG/style/script carrier: `raw` prop or children |
No `nav`, `main`, `ul`, `span`, `a`, or `button` intrinsics. Container tag
override: `tag="header|footer|article|aside|a|button"`.
Special props on all intrinsics: `tw=""` (Tailwind subset), `raw=""` (CSS
declarations, auto-terminated), `cls="name"` (semantic class label),
`gcls="name"` (arbitrary extra class, style it yourself), `id="anchor"` (real
HTML id, `href="#anchor"` works),
`animate={{effect: 'fade'|'slide'|'scale', trigger: 'load'|'scrollIn'}}`.
## sx style props
| Group | Keys | Rules |
| - | - | - |
| Sizes | `w h maxw minh gap size radius pad m` | Number = px, or a `'N'` string with px, %, em, rem, vw, vh, ch (units honored). `w h` also take `'hug'` / `'auto'`. `calc()`, `clamp()`, keywords, two-value gap all **throw**: put them in `raw=` |
| Box | `pad m` | Also take `[v,h]`, `[t,r,b,l]`, partial `{t,r,b,l}`, `'0 auto'` strings |
| Flex | `dir align justify wrap center display flex pos` | `dir` is `'row'` or `'column'` (`'col'` throws). `align`/`justify` validated against Elementor's enums; shorthands `'between'`/`'around'`/`'evenly'` auto-map; **no baseline**, use `flex-end` |
| Grid | `span gridCols` | `gridCols` e.g. `'repeat(3, 360px)'`; add `raw="justify-content:center;"` or grids left-pin |
| Color | `color bg` | Hex, **not** gradient strings |
| Background | `bgImage bgOpts grad` | `bgImage` url or id; `bgOpts` `{color, size:'cover', position:'center center', repeat:'no-repeat', attachment}`; `grad` is an **array** `[angle, from, to]` (a CSS gradient string throws; freeform gradients go in `raw="background-image:...;"`) |
| Border | `border borderColor radius shadow` | `border` is `[w, '#color']`; a bare number is width |
| Type | `size weight font lh ls ta` | Bare-number `lh` (up to 4) and `ls` read as **em** (`ls={-1}` collapses a headline); explicit units honored: `lh="150%"`, `ls="2px"` |
| Misc | `z`/`zIndex` `fit` | `fit` is object-fit |
| Responsive | `tablet={{...}} mobile={{...}}` | Breakpoint overrides with the same keys |
| Escape | `sx={{...}} props` | `sx` merges extra keys; `props` passes raw envelopes |
## Kit helpers (free vars via the prelude, no imports)
* **Fonts load natively**: any Google font named in a style prop is enqueued by
Elementor itself on render (`elementor-gf-*`). Do not add `fontLoader()` for
those (it double-loads). `fontLoader('Family', [400, 700])` (first in the
tree, one per family) is **only** for families Elementor cannot see: fonts
referenced solely inside raw `html` widgets or `raw=` CSS.
* `navBar({logo, links: [[label, href], ...], ctas, accent, ink})`: a complete
header (desktop rail, dropdown mega-menus, mobile hamburger) as one
self-contained html widget, immune to the burger flex-slot bug by
construction. Start here for navs.
* `button(text, href, envelopeProps?)`: real href required (no `'#'`). The
third argument takes **atomic envelopes only** (plain sx values throw, with
recipes). For styled CTAs prefer a styled `` anchor or
`box({...sx, tag: 'a'})`.
* `divider(props)` · `tabs([{label, content}], {active})` · `youtube(url)` ·
`video(url)`
* **Forms (Pro)**, the complete recipe, all four parts required:
```jsx theme={null}
form({ name: 'contact', actions: ['collect-submissions'] }, [
field('name', 'Name'),
field('email', 'Email', { type: 'email' }),
field('msg', 'Message', { textarea: true, rows: 5 }),
formSubmit('Send message'), // form() does NOT add a submit button by itself
])
```
Input types: text, email, number, tel, password, or `textarea: true`. There
is **no radio widget** in atomic Pro forms: option-picker UIs are styled
checkbox fields (one per option, distinct ids) or a select. Use
`collect-submissions`, not `email`, on Elementor 4.2.x with Pro 4.1.0 (the
email action is upstream-broken there). Submissions land in
`wp_e_submissions`.
* `formSuccess({message, sub, accent})`: canned success banner; hides the form
and shows the banner when the Pro ajax submit succeeds. One per page with a
form. The atomic runner shows no sent-state on its own.
## Media manifest
`data/media.manifest.mjs`, run `exjsx media `: default-export an
array of `{slot, file: '/abs/or/rel.jpg'}` or `{slot, src: 'https://...'}`
(fonts: `{slot, type: 'font', file, family, role, weight}`, embedded as a
data-URI). Idempotent by slot; writes `data/media-map.json`. Read ids and URLs
from the map, never hardcode. Full contract:
[Media and fonts](/ultra/media-and-fonts).
## Layout gotchas (each cost a real run)
The prelude provides `Nav`, `Footer`, and `Layout` as built-in free vars; a
project component named `Nav` gets silently shadowed. Name yours `SiteNav`
and `SiteFooter` and import them explicitly.
Row children get `flex: 1` unless width-pinned. Use `w: 'hug'` for
justify-between clusters.
`pos: 'absolute'` plus `raw="inset:0;"` inside a flex parent renders zero
by zero (no width or height of its own). Give the overlay explicit
`w="100%" h="100%"` and the parent `pos: 'relative'`.
A bare `` widget as the third child of a `justify="space-between"`
header still occupies the right flex slot even when its inner button is
`display: none`: the links rail gets centered, not right-pinned (invisible
at 1200, glaring at 1512 and wider; this broke 8 of 10 batch sites). Wrap
it: ``.
Elementor prints its atomic flex CSS later in the body, so a grid set only
in a class loses. Set `display` and grid props via sx (atomic); keep only
extras (auto-rows, dense, bleed) in `raw`.
Text inside rotated or transformed cards needs explicit width (`w: '100%'`
inner, fixed card width); hug-width paragraphs in tilted containers
collapse to one word per line.
Headings inside flex columns need `w: '100%'` or they shrink to max-content
and overflow on mobile.
Absolutes as `left: calc(50% +/- Npx)`, never fixed `left: Npx`; grids
centered. Pages get viewed at 1200 through 2560.
An absolute `
` inside an `html` widget needs `max-width: none`; the
theme clamps it to the wrapper width.
`backdrop-filter` on a nav creates a containing block, so
`position: fixed` overlays inside get trapped. Toggle it off when the
overlay opens (`.open { backdrop-filter: none }`).
Mono or `white-space: nowrap` text wraps nowhere; cap or wrap it for
390px.
Absolute http(s) on the target site, single query param.
`raw="overflow:hidden;"` on the section, or the bleed widens the page.
Body text at roughly `#b0b8c0` or lighter on near-black; judge contrast in
screenshots, not by hex.
## Verify and test (studio commands, never hand-written Playwright)
```bash theme={null}
eu-studio check --pages /,/about/ [--widths 1200,1920,390] # the whole post-deploy gate, one call
eu-studio clicktest --form /contact/ --accordion /pricing/ --burger / --nav /=about
eu-studio doctor # health + auto-heal; NAMES failing stylesheets
eu-studio carry-css --page # make pages independent of a flaky css file store
```
Full reference: [Verify](/ultra/verify) and [Measure](/ultra/measure).
# Components
Source: https://docs.wpos.ai/ultra/components
The intrinsic tags and the kit helpers: containers, text, images, navigation, forms, and embeds.
## Intrinsics: the only tags
| Tag | Notes |
| - | - |
| `box` `div` `col` `row` `section` | Flex containers. `row` forces direction row; `section` renders a real `` |
| `h1` `h2` `h3` `h4` / `heading` | Headings; text props apply; inline ``, ``, `
` children are allowed |
| `text` `p` | Paragraph; `href` renders a **real anchor**, so this is how you make links and buttons-as-links |
| `img` | `src` is a URL string (inline `alt` allowed) or an attachment id (alt comes from the media library) |
| `html` | Raw HTML/SVG/style/script carrier via the `raw` prop or children |
There are **no** `nav`, `main`, `ul`, `span`, `a`, or `button` intrinsics; the
compile error tells you this, believe it. Two rules cover the gaps:
* **Links and buttons** are `` or ``; they render real
anchors. A bare `href="#"` is a placeholder: wire it or use a `#section`
anchor (lint: `placeholder-link`).
* **Semantic containers** come from the `tag=` override on any container:
`tag="header|footer|article|aside|a|button"`.
Special props on every intrinsic: `tw`, `raw`, `cls`, `gcls` (see
[Styling](/ultra/styling)), `id="anchor"` (a real HTML id, so `href="#anchor"`
works), and `animate={{effect: 'fade'|'slide'|'scale', trigger: 'load'|'scrollIn'}}`.
Text intrinsics accept **only** ``, ``, and `
` inline children;
anything else throws at compile rather than silently dropping content. Exactly
one `` per page, and heading levels step one at a time (lint:
`heading-structure`).
## Kit helpers
Kit helpers are free variables via the prelude; no imports needed. They return
kit nodes you can mix freely with JSX children.
### navBar: the safe header
```jsx theme={null}
navBar({ logo, links: [['Work', '/work/'], ['About', '/about/']], ctas, accent, ink })
```
A complete header (desktop rail, dropdown mega-menus, mobile hamburger) as one
self-contained html widget. It is immune to the burger-steals-a-flex-slot bug by
construction (see [Troubleshooting](/ultra/troubleshooting)). Start here for
navs; hand-roll only when the design demands it, and then follow the
burger-wrapper rule religiously.
### Forms (Elementor Pro): the complete recipe
All four parts are required:
```jsx theme={null}
form({ name: 'contact', actions: ['collect-submissions'] }, [
field('name', 'Name'),
field('email', 'Email', { type: 'email' }),
field('msg', 'Message', { textarea: true, rows: 5 }),
formSubmit('Send message'), // form() does NOT add a submit button by itself
])
```
* Input types: `text`, `email`, `number`, `tel`, `password`, or
`textarea: true`.
* **There is no radio widget** in atomic Pro forms. Option-picker UIs are styled
checkbox fields (one per option, distinct ids) or a select.
* Use `collect-submissions`, **not** `email`, on Elementor 4.2.x with Pro 4.1.0:
the email action is upstream-broken there (the validator and the send-runner
disagree). Submissions land in the `wp_e_submissions` table, visible under
**Elementor, then Submissions** in wp-admin.
* Add `formSuccess({ message, sub, accent })` anywhere on the page: it hides the
form and shows a success banner when the Pro ajax submit succeeds. The atomic
runner shows no sent-state on its own. One per page with a form.
### button: the envelope rule
```jsx theme={null}
button(text, href, envelopeProps?)
```
A real `href` is required (no `'#'`). The third argument takes **atomic
envelopes only**; plain sx values throw, and the error carries recipes. For
styled CTAs, prefer a styled `` anchor or `box({...sx, tag: 'a'})`.
### Embeds and structure
* `divider(props)`
* `tabs([{label, content}, ...], {active})`
* `youtube(url)` and `video(url)`
### fontLoader: narrow scope
Google fonts named in a style prop (`font=` or an sx `font-family`) load
**natively**: Elementor enqueues an `elementor-gf-*` stylesheet on render. Do
not add `fontLoader()` for those; it double-loads.
`fontLoader('Family', [400, 700])` (placed first in the tree, one per family) is
**only** for families Elementor cannot see: fonts referenced solely inside raw
`html` widgets or `raw=` CSS. Without it those fall back silently and every
measurement you take is wrong (lint: `font-not-loaded`). Custom font files go
through the [media manifest](/ultra/media-and-fonts).
## Next
The manifest contract for images and font files.
The layout gotchas each of these rules prevents.
# Deploy
Source: https://docs.wpos.ai/ultra/deploy
The build, lint, deploy pipeline: idempotent REST deploys with drift protection and a foreign-store merge guard.
The pipeline is three commands, in order, every time:
```bash theme={null}
npx exjsx build site # JSX -> site/site.bundle.json
npx exjsx lint site # conventions check; --strict for CI
WP_URL=... WP_USER=... WP_APP_PASSWORD=... \
npx exjsx deploy site/site.bundle.json
```
## Lint before every deploy
`exjsx lint` enforces the mechanical subset of the authoring conventions. Rule
families, each with a rule id you can grep for:
* **Errors**: `invalid-envelope` (a style value the server validator would
reject: numbers, colors, units, flex enums).
* **Warnings**: `env-baked-url`, `img-alt`, `raw-atomic-overlap`,
`placeholder-link`, `font-not-loaded`, `heading-structure`, `page-seo`,
`duplicate-page-slug`.
* **Info**: `oversized-raw`, `unnamed-shared-class`, `deep-nesting`,
`empty-container`.
Every finding prints its fix inline. Real output from a production project:
```text theme={null}
project site: 1 page file(s), theme
built site: 1 page(s), 12 variables, 2 fonts
dedup: 237 local styles -> 123 shared classes
lint: 0 error(s), 14 warning(s), 33 info
WARN img-alt [home#e00061]: URL-src image has no alt text
fix: pass alt="..." on
(URL sources carry inline alt)
WARN raw-atomic-overlap [class c-1qwa1g]: raw CSS sets [border-radius] which atomic props cover
fix: move these to tw/sx props; keep raw for nested selectors, pseudo-classes and non-atomic CSS
...
verified: 1313 style envelopes valid (numbers/colors/units/flex-enums) · 3 font families covered (native enqueue or loader): Laygrotesk, Mesotrial, Geistmono · 1 page slug(s) unique
```
The final `verified:` line is a set of **positive assertions**: facts the linter
proved about the bundle (every style envelope valid, every referenced font
family covered, slugs unique). When a gate prints a `verified:` fact, it is
proven; do not re-verify it with hand-rolled scripts.
## Deploy
`exjsx deploy ` performs two kit writes (variables + the shared
class registry) and N page upserts over REST. No wp-cli is needed: the deployer
detects the Elementor version over REST and adapts prop formats per version. It
is **idempotent**: re-run it any time, and unchanged pages are simply
re-stamped.
```text theme={null}
deployed: 12 variables + 123 classes (2 kit writes), 1 page(s)
```
### Flags
| Flag | What it does |
| - | - |
| `--dry` | Plan only. Prints what would be written and per-page actions, no writes |
| `--fast` | Skips the in-deploy CSS self-priming. Use it when a post-deploy `eu-studio check` follows: the check's page visits re-prime the CSS anyway |
| `--only ` | Deploys only the named pages. **All kit writes are skipped** (variables, classes, parts), so shared-class changes lag until a full deploy; the CLI warns exactly about this. Structural changes need a full deploy |
| `--force` | Overwrites pages that drift protection would skip. Never reflex-force; see below |
| `--own-classes` | Declares this bundle the owner of the site's class store, replacing resident classes. See the merge guard below |
`--dry` output:
```text theme={null}
dry-run : would write 8 variables + 4 classes, and:
UPDATE "Home" (/home/) -> existing id 15
```
`--only` warning, verbatim:
```text theme={null}
WARN: --only: shared class registry (123 classes) NOT updated : styles may lag; run a full deploy without --only to sync
```
### Drift protection
On deploy, each page's live tree is compared against the hash stamped at the
last deploy. A mismatch means a human edited the page in the Elementor editor,
and the page is reported as `skipped-drifted` instead of being overwritten.
**Stop when you see it.** Reconcile (use `exjsx decompile` to adopt the live
tree back into JSX) or consciously `--force`; never reflex-force. Drift
detection reads the live tree through wp-cli; without wp-cli the deploy still
works, and the report says
`wp-cli unavailable : drift detection skipped (hand-edit protection off; set EXJSX_WPCLI)`.
### The foreign-store merge guard
Deploying a small side bundle (a probe page, a one-off) to a site that already
hosts another project used to let orphan cleanup delete the resident project's
classes. Now the deployer detects a foreign class store and **merges instead of
wiping**: resident classes the bundle does not declare are preserved, and the
report says so:
```text theme={null}
123 resident class(es) preserved (foreign store : use --own-classes to replace)
```
If the full resident store cannot be read for a safe merge, the deploy refuses
with:
```text theme={null}
deploy: this site's class store holds N classes this bundle doesn't declare : it looks like
another project lives here, and the full store couldn't be read for a safe merge. Deploy to
a fresh site, or pass --own-classes to intentionally replace the store.
```
### Multi-tenant targets: --inline
On shared or multi-site targets, build with `exjsx build site --inline`:
self-contained pages, zero kit writes, no registry or variable clobber.
Single-owned sites should use normal deploys, which own the class registry
namespace (and give you the Class Manager names from `cls=`).
## The loop
Build, lint, deploy, then verify with one call:
```bash theme={null}
npx eu-studio check --pages /,/about/
```
See [Verify](/ultra/verify) for what the gate asserts, and
[Recipes: agent briefs](/ultra/recipe-agent-briefs) for how to budget fix
rounds.
# Alike.page: the magazine cover
Source: https://docs.wpos.ai/ultra/examples/alike
Built live by an AI agent from one prompt: 8 sections in 22 minutes, filmed end to end.
Alike.page gives creators one link that is a store, content hub, and brand page in minutes, with no developer, no design skills, and no transaction fees.
The redesign sets the product like a **magazine front page**: cream stock, a huge red italic Caslon masthead, cover lines instead of feature bullets, and the store demo laid out as an inside spread.
**One prompt. 8 sections. 22 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
The cover section is one save: masthead, cover lines, and the blow-in card all land together and pass the mobile gate on the first try.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Alike.page site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Billow: the flight ledger
Source: https://docs.wpos.ai/ultra/examples/billow
Built live by an AI agent from one prompt: 9 sections in 25 minutes, filmed end to end.
Billow connects a freelance studio's clients, invoices, time, and cash flow, and shows who's profitable, who pays late, and what's actually working.
The redesign flies the finance story as a **flight ledger**: night-sky navy, instrument-panel type, invoice rows as departure boards, and the cockpit section as the product tour. Display face: Lastik.
**One prompt. 9 sections. 25 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
The invoice section lands as a departure board in one save: rows, statuses, and the late-payer highlight, all from the site's real copy.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Billow site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Builders Table: the sprint sheet
Source: https://docs.wpos.ai/ultra/examples/bt26
Built live by an AI agent from one prompt: 8 parts in 24 minutes, filmed end to end.
Builders Table is a 24-hour build sprint in Cape Town: 100 builders, 20 teams, one prototype each, mentors on the floor all night.
The BT-26 edition runs the event as a numbered **sprint run sheet**: PT.01 through PT.07 with an index rail, ink black and acid green, the schedule as a clock strip, and the apply form as the final part.
**One prompt. 8 sections. 24 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
This was the first film in the series, on the original rig. It has no editor segment yet, but it has something the others don't: after shipping, a second prompt makes a live tweak on camera.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4.
A design study of the real Builders Table site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Canopy: the greenhouse ledger
Source: https://docs.wpos.ai/ultra/examples/canopy
Built live by an AI agent from one prompt: 10 sections in 17 minutes, filmed end to end.
Canopy is an SEO autopilot for Squarespace: it builds a keyword strategy, writes the posts, and publishes them to your blog.
The 2026 direction treats content growth like a **greenhouse ledger**: cream paper, deep-green ink, numbered No.01/No.02 section plates, and the product's four tools set as ruled ledger rows. Display face: Martina Plantijn.
**One prompt. 10 sections. 17 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
The fastest build of the set: ten sections in seventeen minutes, with the tool-ledger section written as an 82-line component in a single save.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Canopy site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Housemait: the fridge door
Source: https://docs.wpos.ai/ultra/examples/housemait
Built live by an AI agent from one prompt: 9 sections in 31 minutes, filmed end to end.
Housemait is a family-organiser app: one home for the family calendar, meals, lists and chores, with an AI assistant that lives in WhatsApp.
The redesign reads the product's world literally, as a **fridge door**: warm paper ground, magnet-dot accents, taped-on app screenshots, and the family's week pinned up section by section. Display face: Recoleta.
**One prompt. 9 sections. 31 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
Watch the WhatsApp section land as one save, chat bubbles and all, and the meal-planner screenshots get taped up two saves later.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Housemait site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Mat.: the desk
Source: https://docs.wpos.ai/ultra/examples/mat
Built live by an AI agent from one prompt: 6 sections in 21 minutes, filmed end to end.
Mat. puts live camera cutouts on decorated mats on your iPhone home screen, used by 1700+ users on TestFlight.
The redesign arranges the product like a **working desk**: deep green baize, butter-yellow accents, the app's cutouts scattered as objects on the surface, and a typed letter from the maker as the closing section. Display face: Writer.
**One prompt. 6 sections. 21 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
Six sections in twenty-one minutes: the letter section is set as a full typed page, and the mobile gate passes with the desk objects re-stacked for 390 px.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Mat. site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# AI-Built Elementor Sites: 8 Real Examples, Filmed Live
Source: https://docs.wpos.ai/ultra/examples/overview
Eight real sites, each redesigned and shipped live by an AI agent from a single prompt, filmed end to end, with the full pages to scroll.
All eight films also play on the launch page at
[exjsx.dev](https://exjsx.dev), which was itself built with the stack.
Every example in this section was built the same way, on camera:
1. A human typed **one prompt** into Claude Code:
*"Read BRIEF.md and build it exactly as written: set up, launch the live dev
loop, then build the sections IN ORDER, saving after every section. Go."*
2. The agent read a design brief, started
[`exjsx dev --gates`](/ultra/deploy), and built the page **section by
section**: every file save hot-reloaded onto a real WordPress site and
re-ran the [verification gates](/ultra/verify) at 1200, 1920, and 390 px.
3. When the last section landed, the film shows the page opened in the **native
Elementor editor** (a normal, fully editable page) and then a full scroll
of the finished site.
The videos are editorial cuts of the raw sessions: the timelapse segments are
speed-ramped with motion blur, and the **AGENT TIME** chip in the corner burns
the true elapsed build time, so the compression is honest. No human edited any
section; the only human input is the prompt above.
These are **design studies**: real production sites rebuilt with their own
public copy and assets to test the toolchain against real-world content.
All trademarks, copy, and artwork belong to their owners; no affiliation or
endorsement is implied, and none of these studies is published beyond this
documentation.
## The examples
A family-organiser app rebuilt as a warm fridge-door collage. 9 sections,
31 minutes of agent time.
An SEO autopilot for Squarespace as a botanical ledger. 10 sections,
17 minutes.
A creator storefront set like a glossy front page. 8 sections, 22 minutes.
An indie match-3 game unpacked like a board-game box. 8 sections,
25 minutes.
A freelancer finance dashboard as a flight operations sheet. 9 sections,
25 minutes.
A developer portfolio rewritten as its own spec document. 8 sections,
30 minutes.
A photo-widget app arranged like a working desk. 6 sections, 21 minutes.
A 24-hour build-sprint site as a numbered run sheet, the first film in the
series. 8 parts, 24 minutes.
# Michael Pumo: the specification
Source: https://docs.wpos.ai/ultra/examples/pumo
Built live by an AI agent from one prompt: 8 sections in 30 minutes, filmed end to end.
Michael Pumo is a freelance web designer and developer building with modern web technologies from a studio in London.
The portfolio is rewritten as its **own specification document**: numbered 00.0 clauses, mono annotations, testimonials set as file citations, and the project index as a table of contents. Display face: Saans.
**One prompt. 8 sections. 30 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
The citations section turns client quotes into referenced sources. The agent wrote the citation component and its numbering system unprompted from the brief's one-line direction.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Michael Pumo site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Triple Kingdom: the board box
Source: https://docs.wpos.ai/ultra/examples/triple
Built live by an AI agent from one prompt: 8 sections in 25 minutes, filmed end to end.
Triple Kingdom is a hand-drawn match-3 city-builder by a solo UK developer, famously with no ads, ever.
The 2026 edition unpacks the game like a **board-game box**: chunky rounded panels on deep forest green, a tile-merge strip as the hero's spine, a marching character parade, and the real live top score as an arcade chip. Display face: Londrina Solid.
**One prompt. 8 sections. 25 minutes of agent time.** Every save in
the film hot-reloads the real WordPress page and re-runs the verification gates
at 1200, 1920, and 390 px. The AGENT TIME chip shows true elapsed time through
the timelapse.
This is the film where the ship-signal rule was forged: take one ended early when the agent tagged the footer stub too soon. Take two, shown here, runs clean.
## The finished page
Both frames scroll: the full page at desktop 1440 px and mobile 390 px.
## How it was built
* The agent followed a written design brief with the section order, the design
direction above, and the site's real copy and assets staged locally.
* [`exjsx dev --gates`](/ultra/deploy) ran the loop: save → compile → deploy →
hot reload → [gates](/ultra/verify), a few seconds per save.
* Sections are plain [JSX components](/ultra/components); fonts ship via one
`@font-face` carrier ([Media and fonts](/ultra/media-and-fonts)).
* The finished page is native Elementor V4, and the film ends inside the editor.
A design study of the real Triple Kingdom site, rebuilt with its public copy and
assets to test the toolchain. No affiliation with or endorsement by the
original brand; the study is not published beyond this documentation.
# Elementor + AI Agents: FAQ (Claude Code, Cursor, MCP)
Source: https://docs.wpos.ai/ultra/faq
Short factual answers to the questions people actually ask about building Elementor sites with Claude Code, Cursor, Codex, or any MCP agent.
## Can Claude Code build an Elementor site?
Yes. With [exjsx](https://exjsx.dev), Claude Code writes typed JSX and the
compiler turns it into native, editable Elementor V4 pages over REST, with no
copy-pasting JSON, no HTML blobs. The `--gates` dev loop re-verifies every save
at 1200/1920/390 px, so the agent finds layout breakage before you do. The
whole stack is open source and MIT-licensed; start at the
[quickstart](/ultra/quickstart).
## Is there an MCP server for Elementor?
Yes. **elementor-ultra-mcp** exposes roughly 90 MCP tools covering pages,
widgets, global classes, variables, media, forms, and rollback. See the
[install guide](/ultra/install). On top of it sits the **exjsx** compiler and
CLI, which most agents should prefer for authoring whole pages: one typed JSX
file in, a verified Elementor page out.
## Can Cursor or Codex build Elementor pages?
Yes. Nothing in the stack is Claude-specific: any MCP-capable agent (Cursor,
Codex, Claude Code, and others) can drive the same tools and the same
`exjsx` CLI. Setup is a single paste-to-agent prompt or the interactive wizard, and
both are on the [install page](/ultra/install).
## Are AI-built Elementor pages editable in the normal editor?
Yes. The output is native Elementor V4 content (real atomic widgets, global
classes, and variables), not shortcodes or raw HTML embeds. A human can open
any agent-built page in the standard Elementor editor and keep working, and the
CSS lifecycle guards in the [companion plugin](/ultra/plugin) keep agent
deploys and editor sessions from stepping on each other. There is no lock-in:
uninstalling the toolchain leaves ordinary Elementor pages behind.
## Is exjsx free?
Yes. Everything is MIT-licensed (the WordPress companion plugin is GPL), runs
entirely on your machine, and needs no cloud service and no API key from us.
You only pay for whatever AI agent you already use.
## How is this different from asking AI to write HTML into an Elementor HTML widget?
An HTML widget gives you one opaque blob: clients can't edit it in the editor,
its inline CSS duplicates rules the theme already ships, and it collides with
plugin stylesheets in ways that are painful to debug. exjsx instead compiles to
the same native widget tree the editor itself produces, so styles go through
Elementor's class and variable system, responsive behavior uses real
breakpoints, and every element stays individually editable. The verification
gates then check the rendered result at three widths, something a pasted HTML
blob never gets.
## Does it work with free Elementor?
Yes. Free Elementor is enough for the whole authoring loop; the stack is
tested against Elementor 4.1.4 and 4.2.0. Elementor Pro is only needed for
Pro features (forms with submissions, popups, theme builder), which the
toolchain also supports when Pro is present.
# Install
Source: https://docs.wpos.ai/ultra/install
The package matrix, requirements, and pointing the stack at a real WordPress site.
## Packages
| Package | Version | Installs | You get |
| - | - | - | - |
| `@algorismus/elementor-jsx` | 1.6.0 | `npm i -D @algorismus/elementor-jsx` | The compiler and the `exjsx` CLI (`init`, `api`, `build`, `deploy`, `lint`, `media`, `decompile`, `inspect`, `watch`) |
| `@algorismus/elementor-ultra-studio` | 0.5.0 | `npm i -D @algorismus/elementor-ultra-studio` | The `eu-studio` CLI: provisioning, health doctor, verification gates, measurement toolkit |
| `@algorismus/elementor-ultra-playground` | 1.2.0 | `npx @algorismus/elementor-ultra-playground` | A local WordPress + Elementor + companion plugin, booted from a snapshot |
| `@algorismus/create-elementor-ultra` | 1.0.0 | `npx @algorismus/create-elementor-ultra` | An interactive wizard that verifies your site, wires your AI client's MCP config, and installs a skills knowledge base |
## Requirements
* **Node.js 18 or newer** (the playground needs 18.17+).
* **Playwright** for the verification gates (`eu-studio check`, `verify`,
`clicktest`, `measure`): `npm i playwright`, or point the
`EXJSX_IT_PLAYWRIGHT` environment variable at an existing install's entry
file.
* **python3 with PIL and numpy** for the photometric tooling
(`eu-studio pixeldiff`, `measure compare`). Everything else is pure Node.
* **A WordPress site with Elementor 4.1.4 or newer**, V4 atomic experiments on,
plus the companion plugin (below). The compiler is certified on Elementor
4.1.4 through 4.2.1; version adapters translate prop-format changes per
version. The [playground](/ultra/playground) ships all of this pre-baked.
## Using a real WordPress instead of the playground
The whole pipeline targets any WordPress over plain REST. Three environment
variables form the contract everywhere (`exjsx deploy`, `eu-studio`, the MCP
server):
```bash theme={null}
export WP_URL=https://your-site.com
export WP_USER=admin
export WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
```
`eu-studio` resolves its target in this order: `--url`/`--user`/`--app-password`
flags, then the `WP_URL`/`WP_USER`/`WP_APP_PASSWORD` environment, then the local
playground's baked defaults.
In wp-admin, open **Users, then Profile, then Application Passwords** and
create one. This is standard WordPress auth; no extra auth plugin is needed.
On non-SSL sites, WordPress only accepts application passwords when
`WP_ENVIRONMENT_TYPE` is set to `local` in `wp-config.php`. The doctor
names this exact condition when auth fails.
The deploy and verification seams live in the **elementor-ultra-mcp**
companion plugin (GPL-2.0-or-later). Get the zip from the
[GitHub releases of the elementor-ultra-mcp repo](https://github.com/Algorismus-io/elementor-ultra-mcp),
upload it in **Plugins, then Add New**, and activate it. The playground
snapshot already contains it. What it adds is documented in
[The companion plugin](/ultra/plugin).
In **Elementor, then Settings, then Features**, activate the V4 atomic
elements, classes, and variables experiments. The plugin's
`site/capabilities` endpoint reports their state, and `eu-studio status`
reads it:
```text theme={null}
✓ https://your-site.com : site 200, Elementor 4.2.1 + Pro, seam up
```
```bash theme={null}
npx exjsx build site
npx exjsx deploy site/site.bundle.json
```
No wp-cli is required for deploys: the deployer detects the Elementor
version over REST and writes everything through the plugin's endpoints. Two
optional features degrade gracefully without wp-cli and say so in the deploy
report: drift detection (hand-edit protection) and the SEO runtime install.
Setting up an AI agent rather than a human workflow? `npx
@algorismus/create-elementor-ultra` walks all of the above interactively,
verifies each step live, writes the MCP config for Claude Desktop, Claude
Code, Cursor, Windsurf, or VS Code, and installs a skills knowledge base the
agent reads to run itself.
## Next
The end-to-end local loop.
One-command bench sites with eu-studio newsite.
# Measure
Source: https://docs.wpos.ai/ultra/measure
The measurement toolkit: seven measure modes, the pixeldiff fidelity score, and the bench harness.
Clone runs and design reviews live on measurements: screenshots, section
rhythm, element geometry, text ink, font paint, reference diffs, copy
extraction. `eu-studio measure` covers all of it; never hand-write Playwright or
PIL scripts for these.
## The seven modes
```text theme={null}
usage: eu-studio measure : clone-run measurement toolkit (never hand-write playwright/PIL for these)
shots --page > --widths 1512,390 [--out ] [--full] [--crop x,y,w,h] scroll-settled screenshots (fullPage default; --crop = doc-space region)
sections --page > [--width 1512] section rhythm scan: {sel, top, height} sorted by top
geom --page > --selector '' [--width 1512] [--all] rect + display/position/font for first (--all: ≤40) match(es)
ink --page > --selector '' [--width 1512] text ink extents vs element box (nowrap escapes, tracking drift)
fonts --page > [--width 1512] which families ACTUALLY painted (width-vs-fallback probe)
compare --ours --ref [--out ] [--bands 900] side-by-side band sheets + per-band mean |Δ| table
copytext --html [--min-len 3] deduped, attributed copy inventory from saved HTML
```
What each answers:
| Mode | The question it answers |
| - | - |
| `shots` | What does the page actually look like at these widths? (Scroll-settled, animation-finished captures) |
| `sections` | What is the page's vertical rhythm? Where does each section start and how tall is it? |
| `geom` | Where exactly is this element, and with what display, position, and font? |
| `ink` | Does the rendered text ink escape its element box? (Nowrap escapes, tracking drift) |
| `fonts` | Which font families **actually painted**, versus silently falling back? (Width-vs-fallback probe) |
| `compare` | Side by side, band by band, how far is our render from the reference? |
| `copytext` | What copy does the reference page contain, deduped and attributed, ready to reproduce verbatim? |
Real `sections` output against a production build:
```bash theme={null}
eu-studio measure sections --page /
```
```text theme={null}
✓ 10 sections under div.elementor-element (page 8131px)
{"url":"http://127.0.0.1:8927/","width":1512,"root":"div.elementor-element","pageHeight":8131,
"sections":[{"sel":"section.elementor-element","top":0,"height":900},
{"sel":"section.elementor-element","top":900,"height":880},
{"sel":"section.elementor-element","top":1780,"height":638}, ...]}
```
Review shots at wide widths, not just 1200: `measure shots --page / --widths
1920,390`. Wide screens expose nav and section alignment bugs that 1200
hides, and real users browse at 1512 to 2560.
## pixeldiff: the photometric fidelity score
Anchors measure geometry; pixeldiff measures **appearance**: gradients, art,
type ink.
```text theme={null}
usage: eu-studio pixeldiff --page-url or url> --reference [--keep-shots ] [--exclude y0-y1:why[,y0-y1:why...]]
```
Method: the page is captured as **viewport slices** at the reference's width
(full-page captures of tall pages deterministically drop bottom-of-page image
paint, so it never diffs against one), scroll-settled and animation-finished,
then each slice is compared against the corresponding crop of the reference.
Comparison runs through python3 with PIL and numpy.
The score is per-slice mean absolute luminance delta (0 to 255) plus the worst
50px-band mean, aggregated page-wide. Empirical interpretation bands:
| Score | Reading |
| - | - |
| 3 or less | Near-identical |
| 3 to 8 | Faithful; the residue is font rendering and anti-aliasing noise |
| 8 to 20 | Visible deviations |
| over 20 | Structurally different |
### Exclusion masks
`--exclude y0-y1:why` drops reference-coordinate row bands from the mean and the
worst-band scan, reporting them separately, so the score reflects what the build
was actually **asked** to match. The `why` label is mandatory context for
whoever reads the score.
This exists because of a real incident: an agent spent eight minutes chasing a
score of 8 that was mathematically unreachable; the worst band (61.6) was a
deviation the brief itself mandated, and the gate had no way to know. Mask the
mandated deviations, then chase the score.
## bench: scoring whole scenario runs
For repeatable evaluation of agent builds, `eu-studio bench` scores a scenario
directory (reference render, anchors with presence floors and paint bands, a
brief template) by running geometric verify plus photometric pixeldiff, and
appends one record per run to `runs.jsonl`. `bench compare` tabulates runs. See
`eu-studio bench` for the scenario layout.
## Next
The measurement modes assembled into the faithful-rebuild flow.
The structural and interaction gates.
# Media and fonts
Source: https://docs.wpos.ai/ultra/media-and-fonts
The media manifest: hash-cached image sideloading, custom font embedding, and the media map pages read from.
Production assets go through the media manifest, not hardcoded URLs. The
manifest sideloads images into the WordPress media library and embeds custom
fonts, then writes a map file your theme and pages read at build time.
```bash theme={null}
npx exjsx media data/media.manifest.mjs
```
## The manifest contract
`data/media.manifest.mjs` default-exports an array of entries:
```js theme={null}
export default [
{ slot: 'hero', src: 'https://example.com/hero.jpg', alt: 'Hero shot' }, // remote image
{ slot: 'logo', file: 'assets/logo.svg', alt: 'Brand logo' }, // local image
{ slot: 'head-font', type: 'font', file: 'assets/Fraunces.woff2',
family: 'Fraunces', role: 'head', weight: 600 }, // local font
];
```
* **`{ slot, src }`** fetches a remote image and uploads it to the target site's
media library. Rights note: this fetches third-party URLs verbatim; run it
only for assets you own or license.
* **`{ slot, file }`** uploads a local file. Supported image formats: jpg, jpeg,
png, webp, svg, gif.
* **`{ slot, type: 'font', file, family, role, weight }`** embeds a local font
file (woff2 or woff) as a base64 data-URI in the map. WordPress blocks woff2
uploads by default; a data-URI `@font-face` needs no server configuration.
The compiler emits the `@font-face` carrier for you; this path is for
custom licensed font files only. Google fonts load natively via style props
(see [Components](/ultra/components)).
* `alt` on image entries is set as the attachment's alt text.
## Idempotency by slot
Every entry is keyed by its `slot`; attachments are slugged `exjsx-`.
Re-running the command is safe:
* A local file whose bytes match the map's hash is skipped with zero network
calls (`SAME`).
* A `src` entry whose slug already exists in the library is kept (`KEEP`).
* A local file with **changed bytes** replaces the stale attachment (`REUP`),
so a recolored logo actually reaches the site instead of silently serving the
old one.
```text theme={null}
FONT head-font -> Fraunces (48 KB, embedded)
UP hero -> id 231 (184 KB)
SAME logo -> id 187 (hash match, skipped)
media-map -> data/media-map.json (3 slots). Rebuild + redeploy to use it.
```
## The map: data/media-map.json
The command writes `data/media-map.json` (the optional second CLI argument
overrides the path): `{ slot: { id, url, alt } }` for images and
`{ slot: { type: 'font', family, role, weight, url } }` for fonts. **Read ids
and URLs from the map; never hardcode them.**
```jsx theme={null}
import map from '../data/media-map.json' with { type: 'json' };
```
Two alt rules, both enforced:
* URL-src `
` takes inline `alt`; never leave it empty (lint: `img-alt`).
* Attachment-id images take alt from the attachment only; the compiler throws
if you pass `alt` there, and the error carries the recipe.
Never bake dev-host URLs (`localhost:...`, `127.0.0.1:...`) into content that
will deploy elsewhere. Sideload to the target site, or key URLs off `WP_URL`
at build time. `exjsx lint` catches this (`env-baked-url`); a portability
audit once found three hardcoded stack URLs this way.
## Next
Build, lint, and ship the bundle.
bgImage, bgOpts, and image fitting.
# Elementor Ultra: Build Elementor Sites with AI Agents (MCP)
Source: https://docs.wpos.ai/ultra/overview
Open-source tooling that lets AI agents and developers build real Elementor V4 sites as code: JSX in, native editable pages out.
Elementor Ultra is an open-source stack for building Elementor V4 sites as code.
You (or an AI agent) write small typed JSX components, a compiler turns them into
native Elementor V4 atomic JSON, and a one-shot deployer writes them to WordPress
over REST. The result is a normal Elementor page: fully editable in the Elementor
editor, with no lock-in and no proprietary runtime on the site.
The two-minute version lives at **[exjsx.dev](https://exjsx.dev)**, a landing
page built with the stack itself, with films of AI agents building eight real
sites end to end (17 to 31 minutes of agent time each, zero human edits). This is
the launch film (sound on):
It was built agent-first. Typed props fail at build time, an authoritative
server-side validator rejects bad trees before they save, and verification gates
replace hand-written browser scripts. Humans get the same ergonomics; the design
center is the agent.
Elementor Ultra is MIT-licensed open source from the same team, separate from
the WPOS product documented in the other tabs. It is not affiliated with or
endorsed by Elementor Ltd; "Elementor" is their trademark, and this is an
independent toolchain that targets their page format.
## The architecture in one pass
1. **JSX components.** Pages live in an fs-project: `pages/*.page.jsx`, shared
`components/`, design tokens in `theme.mjs`. See [Projects](/ultra/projects).
2. **Compiler.** `exjsx build` compiles JSX to Elementor V4 atomic JSON, dedupes
repeated styles into shared global classes, and writes one reviewable bundle
file. Every change is a git diff, not a 4,000-line JSON blob.
3. **REST deploy.** `exjsx deploy` writes variables, classes, and pages to the
site over REST, idempotently, with drift protection for hand-edited pages.
See [Deploy](/ultra/deploy).
4. **Native Elementor V4 pages.** What lands on the site is standard atomic
Elementor content. Open it in the editor, hand it to a client, or keep
iterating from source.
A site built entirely by an AI agent with this stack, from a written brief to a
verified deploy (scroll-through of the live result):
## The four packages
| Package | Version | Role |
| - | - | - |
| `@algorismus/elementor-jsx` | 2.0.0 | The JSX to Elementor V4 compiler plus the `exjsx` CLI: build, lint, deploy, media, decompile. 100% coverage of free-core V4 atomic elements (native `e-grid`), `motion` interactions, `defineComponent` → native Elementor components |
| `@algorismus/elementor-ultra-studio` | 0.5.1 | The agent build environment (`eu-studio`): provisioning, verification gates, measurement toolkit |
| `@algorismus/elementor-ultra-playground` | 1.2.0 | Instant local WordPress + Elementor via WASM, booted from a pre-baked snapshot |
| `@algorismus/create-elementor-ultra` | 1.0.0 | Interactive scaffolder: wires the stack into your AI client and your site |
A companion WordPress plugin, **elementor-ultra-mcp**, ships bundled inside the
playground snapshot (and as a zip for real sites). It provides the REST seams the
deployer and gates talk to, plus CSS lifecycle guards that keep agent deploys and
human editor sessions from destroying each other's work. See
[The companion plugin](/ultra/plugin).
## Who it is for
* **AI agents** that need a real API surface for Elementor instead of a blank
file: typed components, build-time errors, one-call verification gates, and
briefs designed for one-shot builds.
* **Developers and agencies** who want Elementor sites in version control:
reviewable diffs, CI-friendly linting, idempotent deploys, and a local
environment that boots in seconds.
## Start here
Local WordPress to a deployed, verified page in a few minutes.
The package matrix, requirements, and using a real WordPress site.
The fs-project layout: pages, components, theme tokens.
The sx vocabulary, the Tailwind subset, and the raw CSS escape hatch.
The post-deploy gate: structural matrix, interaction tests, doctor.
The complete one-page authoring reference.
## Community
Everything is MIT-licensed and developed in the open at
[github.com/Algorismus-io/elementor-jsx](https://github.com/Algorismus-io/elementor-jsx).
* **Questions and ideas:** [GitHub Discussions](https://github.com/Algorismus-io/elementor-jsx/discussions)
* **Bugs:** [open an issue](https://github.com/Algorismus-io/elementor-jsx/issues)
* **Chat:** [join the Slack](https://join.slack.com/t/wpcursor/shared_invite/zt-3vzlb15hd-JsLeBc_WA~ZFlkWCFnxc6A)
* **Contributing:** [CONTRIBUTING.md](https://github.com/Algorismus-io/elementor-jsx/blob/main/CONTRIBUTING.md)
* **Talk to the founder:** [book 30 minutes](https://cal.com/algorismus/30min)
# Playground
Source: https://docs.wpos.ai/ultra/playground
Instant local WordPress + Elementor: a WASM playground booted from a pre-baked snapshot, crash-supervised.
```bash theme={null}
npx @algorismus/elementor-ultra-playground
```
One command gives you a local WordPress with Elementor and the companion plugin,
ready for `exjsx deploy` and the MCP server. No Docker, no PHP, no MySQL, no git
clone: it runs on WordPress Playground (PHP-WASM, pure Node) and hydrates from a
**pre-baked site snapshot**, skipping the minutes a cold WordPress + Elementor
install costs. First run downloads a \~50 MB snapshot once (cached under your
home directory); later boots take a few seconds.
```text theme={null}
WordPress http://127.0.0.1:8899/wp-admin (admin / password)
Elementor 4.2.1 (free) + Elementor Ultra companion plugin
For the MCP server and `exjsx deploy`:
WP_URL=http://127.0.0.1:8899
WP_USER=admin
WP_APP_PASSWORD=...
```
## What is inside the snapshot
WordPress (Playground build), Elementor 4.2.1 (free), the elementor-ultra
companion plugin, pretty permalinks, `WP_ENVIRONMENT_TYPE=local` baked into
`wp-config.php`, the V4 atomic / classes / variables experiments active, and a
minted local-dev application password.
The application password is baked into the snapshot and printed on every boot.
It is a throwaway credential for a local-only dev site. Never reuse it
anywhere real.
## Flags and environment
| Flag | Meaning |
| - | - |
| `--port ` | Serve on a different port (default `8899`) |
| `--fresh` | Wipe the cached site and re-hydrate from the snapshot |
| `--provision` | Skip the snapshot; provision a fresh site from bundled assets (needs network for wordpress.org) |
| `--tarball ` | Hydrate from a local snapshot tarball instead of downloading |
| `--dir ` | Hydrate into a specific directory instead of the cache |
Environment: `ELEMENTOR_ULTRA_HOME` overrides the cache location; `ULTRA_PORT`
sets the port.
## Crash supervision
PHP-WASM workers can crash (a bad CSS request was a reliable trigger in the
field). The playground runs supervised: a crashed worker respawns
automatically. If a request suddenly fails, wait about 15 seconds and retry
before diagnosing; right after a respawn the worker answers health checks while
still warming for tens of seconds.
## The single-worker reality
The site is served by a single PHP-WASM worker: requests queue, and one heavy
request blocks the rest. Treat a playground site as one-user-at-a-time. Two
consequences worth knowing:
* Right after boot or a bounce, allow generous timeouts; the provisioning
tooling retries with growing timeouts for exactly this reason.
* Missing static files 301-redirect to the homepage on Playground-style hosts,
which makes a dead stylesheet look alive to naive checks. The companion
plugin's static-404 guard fixes this (see [Plugin](/ultra/plugin)), and the
[doctor](/ultra/verify) checks content-type, not just status.
Do not open playground-hosted pages in the Elementor editor. Editor sessions
are the known trigger for global-class store wipes on this stack. If styles
vanish after an editor visit, redeploy the bundle and run `eu-studio doctor`.
On real sites the companion plugin's store guards make editor coexistence
safe; the playground is a dev bench, treat it like one.
## Resetting and multiple sites
```bash theme={null}
npx @algorismus/elementor-ultra-playground --fresh # wipe + re-hydrate
```
Run several isolated sites by giving each its own port and directory:
```bash theme={null}
npx @algorismus/elementor-ultra-playground --port 8901 --dir ~/sites/site-a
npx @algorismus/elementor-ultra-playground --port 8902 --dir ~/sites/site-b
```
For a site that also needs the plugin patched to the latest checkout and
Elementor Pro activated, use [`eu-studio newsite`](/ultra/provisioning), which
wraps the boot and verifies the result.
# The companion plugin
Source: https://docs.wpos.ai/ultra/plugin
elementor-ultra-mcp on the WordPress side: the REST seams agents deploy through, and the CSS lifecycle guards that make agent and editor coexistence safe.
The **elementor-ultra-mcp** companion plugin (GPL-2.0-or-later) is the
WordPress side of the stack. It does two jobs: it exposes the REST seams that
`exjsx deploy`, `eu-studio`, and the MCP server write through, and it hardens
the CSS lifecycle around Elementor's atomic pages so automated deploys and human
editor sessions can coexist. It ships bundled in the
[playground](/ultra/playground) snapshot and as a zip in the
[elementor-ultra-mcp releases](https://github.com/Algorismus-io/elementor-ultra-mcp)
for real sites.
All routes live under `elementor-ultra/v1`, authenticated with standard
WordPress application passwords and capability checks. The plugin loads
defensively: it serves what it can regardless of Pro presence or Elementor
version, and `site/capabilities` always answers so health checks work even on a
degraded site.
## The REST seams
| Area | Routes (summary) | What they add |
| - | - | - |
| Capabilities | `site/capabilities` | Elementor and Pro versions, experiment states, plugin version. The first call every tool makes |
| Documents | `documents`, `save`, `replace-tree`, `dry-run`, `rollback`, `backups`, `duplicate`, `export` | Validated page writes with server-side envelope validation (a 422 names the offending envelope), automatic backups, and rollback |
| CSS priming | `documents//prime-css` | Makes V4 atomic CSS files actually exist after a headless save. Atomic CSS only renders on frontend hooks, so a REST-saved page has no CSS until primed; this endpoint runs the render dispatch in-process and asserts the files exist and are non-empty |
| Cache | `cache/regen` | V3 per-post CSS regen plus the full Elementor CSS flush, executed in-process (the only reliable way: it must run as the uid that owns the css upload dir, and it verifies deletion actually happened instead of trusting Elementor's success string) |
| Design system | `design/classes` (+ `usage`, `restore`), `design/variables` (+ batch, restore), `design/global-colors`, `design/global-fonts`, `design/fonts` (+ install, upload-zip), `design/deploy`, `design/element-defaults` | The global class store, variables, and fonts as a governed API, with backups and restore |
| Content plumbing | `media` (+ `upload`, `sideload`), `templates`, `nav/menus`, `nav/bind-widget`, `schema/*`, `ops/log`, `batch/*` | Media with mime gating and SVG sanitization, template import/export, menu binding, widget schemas, an operations log, batch plan/apply |
Notes agents hit in practice: `design/classes` is cursor-paginated (default
limit 25 with `next_cursor`); SVG uploads are sanitized with Elementor's own
sanitizer before import; media sideload byte-sniffs extensionless CDN URLs and
rejects anything that is not a known image type.
## The CSS lifecycle guards, in plain words
Elementor's atomic CSS pipeline has failure modes that only show up under
automation. The plugin defuses each one at the seam where it happens.
### Post-scoped invalidation
Priming one page must not invalidate every other page's CSS. The primer scopes
its cache invalidation to the post being primed (`local` and `global` per post
id, mirroring Elementor's own internal callers), instead of the unscoped form
that made multi-page sites play whack-a-mole: priming page B silently
invalidated the file page A's prime had just written. Only the shared `base`
stylesheet stays unscoped, because every page regenerates it identically.
### The license-churn defuse
Elementor runs a **full CSS cache wipe** whenever the Pro license option is
updated or deleted. On any site with Pro installed but unlicensed (every local
and dev stack), admin and editor sessions re-check the license on heartbeats,
churning that option: admin-ajax, license option write, sitewide CSS wipe, "the
whole site lost styling". The plugin detaches that wipe-on-churn hook. A real
license change still takes effect on the next save, deploy, or regen.
### Global-classes wipe protection: backup plus restore
The class store is the styling for every deployed atomic page, and a normal
Elementor editor visit has been observed to empty it. Defense in two parts:
* The plugin continuously snapshots the store whenever it is written
**non-empty** (an empty store never overwrites a non-empty backup).
* `POST /elementor-ultra/v1/design/classes/restore` writes the snapshot back.
### The mass-deletion guard (409)
The editor keeps an in-memory copy of the class store and PUTs the whole store
back on save, last-writer-wins. If its copy was incomplete (a raced classes
fetch at editor load), that save silently deletes most of the classes sitewide.
The plugin refuses any single write that would delete more than half of a
non-trivial store, returning a 409 with code `EMCP_MASS_DELETE_BLOCKED` and a
message that explains the situation and the escape hatches: retry with the
header `X-EMCP-Allow-Mass-Delete: 1` if the deletion is intentional, and the
restore endpoint if it was not. A failed save the user can see beats a silent
wipe.
### The static-404 guard
Hosts that route unresolved static files into WordPress (the WASM playground;
any standard rewrite setup) let `redirect_canonical()` 301 a missing stylesheet
to the homepage, so HTTP clients see a healthy `200 text/html` for a sheet that
is gone, browsers silently drop it, and health checks read false positives. The
plugin makes any request for a missing asset file under `wp-content` or
`wp-includes` return a real 404. Files that exist on disk are untouched.
### The publish quarantine
A conversion or build that fails its verify gate marks the post quarantined,
and the plugin enforces it at `wp_insert_post_data`: the one seam every publish
flows through (core REST, wp-admin, WP-CLI, MCP). A quarantined post is forced
back to draft on any publish attempt, no matter the vector, until a passing
re-run or a human clears the flag.
## Why the editor and agents can coexist
Put together: deploys write through validated endpoints with backups and
rollback; priming is scoped so pages do not eat each other's CSS; the class
store is continuously backed up, guarded against editor mass-deletes, and
restorable; license churn cannot wipe the site; and dead assets fail loudly
instead of lying. An agent can deploy while a human owns the editor, and the
failure modes that used to silently unstyle a site now either cannot happen or
leave a visible, recoverable trace.
On the playground specifically, still avoid opening deployed pages in the
editor; the dev bench favors fast iteration over full guard coverage. See
[Playground](/ultra/playground).
# Projects
Source: https://docs.wpos.ai/ultra/projects
The fs-project layout: pages, parts, components, theme tokens, and page meta.
The canonical form of an elementor-jsx site is an **fs-project**: a directory
where the layout is the site wiring, and each file holds exactly one concern.
`exjsx build ` discovers it, validates the whole layout up front, and
throws one error listing every problem with its fix.
```text theme={null}
mysite/
site.config.mjs (optional) project config only: { name?, template? }
theme.mjs (optional) defineTheme tokens only: the one place brand values live
pages/ CONTENT: *.page.jsx files only (discovery errors on strays)
home.page.jsx default-exports a component; export const meta = {...} per page
about-us.page.jsx slug = filename (nested dirs join with '-'); meta.slug overrides
locations/[city].page.jsx dynamic: export const data = () => [{slug, title, seo?, props?}]
parts/ CHROME: .part.jsx, type in header|footer|single|archive|error404|popup
header.part.jsx
components/ shared section components (imported by pages)
data/ data arrays, media.manifest.mjs, media-map.json
```
Pages hold content, parts hold chrome, theme holds tokens, `data()` holds data.
A file that mixes concerns still compiles; the layout exists so the seams stay
reviewable. Small sites may instead use a single `site.jsx` default-exporting
`defineSite`; it runs through the same pipeline.
## Page meta
Every `*.page.jsx` default-exports a component and may export `meta`:
```jsx theme={null}
export const meta = {
title: 'About us', // page title
slug: 'about', // optional; defaults to the filename
seo: { title: 'About | Brand', description: '...' }, // ship this on every page
template: 'elementor_canvas', // or 'elementor_header_footer'
};
export default ({ theme }) => (
);
```
* `slug` values must be unique and kebab-case (`exjsx lint` enforces both, rule
ids `duplicate-page-slug` and `page-seo`).
* `template` defaults to `elementor_canvas` for standalone pages. When the
project has `parts/` chrome, pages default to `elementor_header_footer`
automatically; an explicit `meta.template` wins.
## Theme tokens
`theme.mjs` is the one place brand values live. Components receive it as the
`{ theme }` prop:
```jsx theme={null}
export default defineTheme({
name: 'mysite',
color: { ink: '#1A1A2E', primary: '#2563EB', paper: '#FAFAF7', muted: '#5B616E' },
font: { head: 'Fraunces', body: 'Inter' },
mode: 'literal',
});
```
```jsx theme={null}
export default ({ theme: t }) => (
Never re-declare hex
);
```
`mode: 'literal'` emits colors and fonts directly and renders on every supported
Elementor version. `mode: 'var'` makes text color and font live-editable in
Elementor's Class Manager (variables); backgrounds degrade to literals
automatically on 4.1.4, and the compiler handles that for you. Start literal;
opt into `var` when you need live binding.
## The prelude: import nothing
The build auto-injects a curated authoring surface into every built file, so
components and pages usually need **zero imports**. `defineSite`, `defineTheme`,
`fontLoader`, `tabs`, `dyn`, the kit builders, and the component library all
resolve as free variables. Unused names tree-shake away, and your own binding of
a name always wins, so the prelude can never break explicit code.
Everything else comes from one bare specifier:
```jsx theme={null}
import { DIM, PDIM, node } from 'elementor-jsx'
```
Short generic names (`S`, `C`, `DIM`, `node`, `abs`, `hover`) are deliberately
not auto-injected: a typo silently resolving to a helper is worse than a
ReferenceError. They stay explicit-import-only.
The prelude provides `Nav`, `Footer`, and `Layout` as built-in free variables.
A project component named `Nav` gets silently shadowed by the built-in. Name
yours `SiteNav` and `SiteFooter`, and import them explicitly from your
`components/` directory.
## Component conventions
* Small, props-driven function components; theme arrives via context
(`` plus `useTheme()`), not prop-drilling.
* Any visual pattern used three or more times gets `cls="card"`: the dedup pass
names the shared class, and the Class Manager shows `card`, not `c-1x9fq2`.
* Never reuse a kit-node instance (`tabs()`, `divider()` results). Shared
instances mint duplicate ids and `assertTree` throws. Use a factory:
`const Rule = () => divider({...})`.
* Three or more pages with the same shape: use `fromData(items, map)`, not
copy-paste.
## Next
tw, sx, and raw: the styling ladder.
Intrinsics and kit helpers.
# Provisioning
Source: https://docs.wpos.ai/ultra/provisioning
eu-studio newsite: one command to a fully provisioned build site, plus the target resolution contract.
`eu-studio` is the operational layer around the [playground](/ultra/playground):
it boots sites, verifies them, and encodes the environment fragility lessons so
agents do not rediscover them per session.
## newsite: one command to a build-ready site
```text theme={null}
usage: eu-studio newsite --port --dir (boots + patches plugin + activates Pro + verifies; idempotent)
```
It replaces the manual ritual (boot playground, wait, copy and activate Pro,
hand-patch the plugin) with one idempotent command. What it does, in order:
Boots the playground on the given port and directory. If the port already
serves, it reuses the running site (re-running newsite is a re-verify, not
a re-provision). Cold hydration downloads the snapshot, so the first boot
is allowed several minutes.
The snapshot can carry an older companion plugin than your local
elementor-ultra-mcp checkout. When a local checkout is present, newsite
copies the current plugin files into the site.
If a Pro plugin source is available (`EXJSX_PRO_DIR`, or auto-discovered
from a sibling provisioned site), the files are copied in and Pro is
activated over REST. Without a source it continues free-only and says so.
PHP-WASM caches plugin code in the running worker: files patched after boot
are invisible until a restart, so a site can pass every check while still
executing the old plugin. newsite kills the worker; the crash supervisor
respawns it with the patched files loaded.
It then checks the seam (`site/capabilities`), the static-404 guard, and
that the **running** plugin version matches the patched source on disk. A
mismatch means the bounce failed and the site is executing stale code; the
warning tells you to bounce again:
```text theme={null}
[newsite] WARNING: running plugin X != patched Y : worker is executing STALE code;
bounce it (kill the port listener; the supervisor respawns).
```
The command returns the site's URL, credentials, Elementor and Pro versions,
and the guard states, so a calling agent can proceed without further probing.
## status: one-line health
```bash theme={null}
eu-studio status
```
```text theme={null}
✓ http://127.0.0.1:8927 : site 200, Elementor 4.2.1 + Pro, seam up
```
For the full check list with self-heal, use [doctor](/ultra/verify).
## The target resolution contract
Every `eu-studio` command resolves its target site the same way, in priority
order:
1. **Flags**: `--url`, `--user`, `--app-password`
2. **Environment**: `WP_URL`, `WP_USER`, `WP_APP_PASSWORD`
3. **Baked defaults**: the local playground snapshot's credentials on the
default port
So the same command works against a playground bench, a docker stack, or a real
site with the companion plugin; only the target changes. This is the same env
contract `exjsx deploy` and the MCP server use (see [Install](/ultra/install)).
# Quickstart: Elementor MCP Setup for Claude Code & Cursor
Source: https://docs.wpos.ai/ultra/quickstart
From nothing to a deployed, verified Elementor V4 page on a local WordPress in a few minutes.
This guide boots a local WordPress, scaffolds a project, and ships a page you can
open in the Elementor editor. Everything runs on your machine; no Docker, no PHP,
no database setup.
A real first run, recorded live: one starter prompt into Claude Code, which does the
whole setup itself while the site comes alive alongside (setup timelapsed; the edit near the
end reached the page in 85 seconds):
Prefer the commands yourself? The same flow by hand, in about a minute:
You need Node.js 18 or newer. The verification step also needs Playwright
(`npm i playwright`). See [Install](/ultra/install) for the full requirements.
```bash theme={null}
npx @algorismus/elementor-ultra-playground
```
First run downloads a \~50 MB snapshot once, then boots. You get a local site
with Elementor, the V4 atomic experiments already on, and the companion
plugin active:
```text theme={null}
WordPress http://127.0.0.1:8899/wp-admin (admin / password)
Elementor 4.2.1 (free) + Elementor Ultra companion plugin
For the MCP server and `exjsx deploy`:
WP_URL=http://127.0.0.1:8899
WP_USER=admin
WP_APP_PASSWORD=...
```
The application password printed at boot is baked into the snapshot. It is a
throwaway credential for a local-only dev site. Copy the three values; the
deploy step needs them. Details in [Playground](/ultra/playground).
In another terminal:
```bash theme={null}
npm i -D @algorismus/elementor-jsx
npx exjsx init site
```
```text theme={null}
scaffolded site/
theme.mjs · site.config.mjs · pages/home.page.jsx
next:
exjsx build site
WP_URL=... WP_USER=... WP_APP_PASSWORD=... exjsx deploy site/site.bundle.json
```
Prefer a guided setup that also wires your AI client (Claude Code, Cursor,
Windsurf, VS Code)? Run `npx @algorismus/create-elementor-ultra` instead.
Open `site/pages/home.page.jsx`. It is a small component that receives your
theme tokens:
```jsx theme={null}
export const meta = {
title: 'Home',
seo: { title: 'site', description: 'Built with elementor-jsx.' },
};
export default ({ theme: t }) => (
Hello, Elementor.
This page is compiled from pages/home.page.jsx. Edit it, run the build, deploy, repeat.
);
```
Change the headline, tweak a color in `theme.mjs`, add a section. The
authoring surface is documented in [Components](/ultra/components) and
[Styling](/ultra/styling).
```bash theme={null}
npx exjsx build site
```
```text theme={null}
project site: 1 page file(s), theme
built site: 1 page(s), 8 variables, 2 fonts
dedup: 4 local styles -> 4 shared classes
bundle -> site/site.bundle.json
```
The dedup line is the compiler collapsing repeated styles into shared global
classes, so the Elementor Class Manager stays clean.
```bash theme={null}
WP_URL=http://127.0.0.1:8899 WP_USER=admin WP_APP_PASSWORD= \
npx exjsx deploy site/site.bundle.json --fast
```
```text theme={null}
deployed: 8 variables + 4 classes (2 kit writes), 1 page(s)
```
`--fast` skips the in-deploy CSS priming; the check in the next step
regenerates it through its page visits. Deploys are idempotent: run it again
any time. See [Deploy](/ultra/deploy) for all flags.
```bash theme={null}
npm i -D @algorismus/elementor-ultra-studio playwright
npx eu-studio check --pages /
```
```text theme={null}
✓ / @1200: sw=1200 h1=1
✓ / @1920: sw=1920 h1=1
✓ / @390: sw=390 h1=1
{"pass":true,"structural":{"pass":true,...},"interactions":{"pass":true,"results":[]}}
```
One call, one browser session: every page at three widths, checked for
horizontal overflow, heading structure, console errors, and a set of
forensic probes. See [Verify](/ultra/verify).
Open `http://127.0.0.1:8899/?page_id=` in your browser, or the page in the
Elementor editor on a real site. What you deployed is a native V4 page. On the
playground specifically, avoid opening pages in the editor; see the note in
[Playground](/ultra/playground).
## Next steps
The full fs-project layout and page meta.
Point the same pipeline at any WordPress with the companion plugin.
Structural matrix, interaction tests, and the doctor.
Everything on one page. Also `npx exjsx api` offline.
# Recipe: agent briefs
Source: https://docs.wpos.ai/ultra/recipe-agent-briefs
Writing build briefs that let an AI agent one-shot a site: the API-card-first rule, one-call verification, and fix-round budgets.
A build brief is the document you hand an agent along with a target site. The
difference between a brief that one-shots a four-page site and one that burns an
hour is not the design direction; it is the operating rules. These are the ones
that survived real batch runs. `eu-studio brief` generates a ready-to-run brief
for the current environment; this page is the doctrine behind it.
## The API-card-first rule
Make the agent's first act `exjsx api` (or reading the printed
[API card](/ultra/api-card)), and explicitly forbid grepping framework source,
conventions files, examples, or memory files. The card is the complete
authoring API: intrinsics, sx keys, kit helpers, gotchas. Agents that explore
source instead of reading the card spend their budget on archaeology and still
author against internals that were never public surface.
## One-call verification
Verification is where agent builds bleed time: each hand-written Playwright
probe is a tool call plus a model round-trip. The brief pins the whole gate to
single calls:
```text theme={null}
eu-studio check --pages /,/about/,/pricing/,/contact/ \
--form /contact/ --accordion /pricing/ --burger / --nav /=about
```
One call, one browser session, the full structural matrix plus every
interaction test, in about a minute. After a fix that touches one page, the
brief says to re-check only that page. All measurement needs (screenshots,
section rhythm, geometry, text ink, font paint, diff sheets, copy extraction)
are pinned to `eu-studio measure `; the brief bans hand-written
Playwright and PIL outright.
## Fix-round budgets
Give a hard budget and an exit: **max two fix rounds, then report residuals
instead of chasing.** Convergence loops without a budget do not converge; they
oscillate around anti-aliasing noise. The brief's report format enforces the
exit: page IDs and URLs, the final check JSON verbatim, the screenshot
directory, and accepted deviations as a terse list. No prose narrative; the
JSON is the record.
## Structural diversity guidance
Left to defaults, models converge on the centered-column SaaS formula (hero,
three feature cards, stats band, testimonial, CTA). If you want design, ban the
default in the brief and make diversity testable:
* At most one section per page may be a centered single-column block.
* At least three distinct layout archetypes across the site: split-screen,
asymmetric offset grid, sticky side rail, full-bleed color blocks with hard
seams, oversized display type as layout, horizontal timeline, overlapping
stacked cards, editorial multi-column text, diagonal section breaks, a table
as a design object.
* Vary section rhythm: at least one very tall and one very short section per
page; backgrounds must alternate meaningfully.
* Pages must not share one master section layout with swapped copy.
* The type scale must take a position: huge display contrast somewhere, or a
strict editorial scale, not the safe middle everywhere.
## Trust the gates
Tell the agent explicitly: when `lint` prints a `verified:` fact or `check`
passes a probe, that fact is **proven**. Re-verifying proven facts with
hand-rolled scripts is the most common form of wasted budget. The
positive-assertion design of the gates exists so the agent can build on them.
## Spend thinking where it pays
The brief should direct effort: deep design thought at theme and authoring time
(one-shot the pages well), quick mechanical execution everywhere else.
Screenshots, crops, measurements, and redeploys are mechanical; deliberating
over them is waste. Concretely: write the whole site before the first deploy,
deploy with `--fast`, run one check call, judge shots at 1920 and 390, fix,
targeted re-check.
## Environment rules that prevent derailments
Small print that saves whole sessions, worth copying into any brief:
* The target site is already running; do **not** provision anything or kill any
process.
* The server self-heals on crash; on a sudden request failure, wait about 15
seconds and retry before diagnosing.
* Do not open pages in the Elementor editor on the playground.
* A deploy 422 names the offending envelope; the server validator is
authoritative, fix the named thing.
* If CSS breaks: `eu-studio doctor` once; if the file store keeps flaking,
`carry-css` once at the end.
## Related
What the one-call gate actually asserts.
The measured rebuild flow briefs point at.
# Recipe: clone a site
Source: https://docs.wpos.ai/ultra/recipe-clone-a-site
The faithful-rebuild flow: capture the reference, extract copy and assets, build from tokens, and converge with measured rounds.
This is the flow used to rebuild a production marketing site into a fully
editable Elementor V4 project, faithful to the pixel where it matters and
deliberate where it deviates. It works because every step replaces judgment
calls with measurements.
Rights first: only clone sites you own or have license to reproduce, and only
fetch assets you are entitled to. The tooling fetches what you point it at,
verbatim.
Take scroll-settled screenshots of the reference site at your target
widths. If the reference is reachable, `eu-studio measure shots --page --widths 1512,390` does it (scroll-settled, animations finished);
otherwise capture manually at exact widths. These PNGs are the contract for
the whole run: every later measurement compares against them.
Save the reference page's HTML, then:
```bash theme={null}
eu-studio measure copytext --html reference.html
```
You get a deduped, attributed copy inventory. Reproduce copy verbatim;
paraphrased copy changes line lengths, which changes wraps, which changes
every geometry measurement downstream.
Collect the images and licensed font files into `assets/`, write the
[media manifest](/ultra/media-and-fonts), and run `exjsx media`. Confirm
which families actually paint on the reference with
`eu-studio measure fonts`; sites routinely declare families they never
render.
Before any page code, pin down `theme.mjs` (colors, fonts), the type scale,
and a section-by-section outline with the reference's y-coordinates (from
`measure sections` against the reference, or read off the captures). If an
agent does the build, this becomes its [build brief](/ultra/recipe-agent-briefs)
with an `anchors.json` of section targets.
The normal loop: fs-project, tw-first styling, `lint`, `build`,
`deploy --fast`. Write the whole page before the first deploy; per-section
deploys waste rounds.
```bash theme={null}
eu-studio check --pages /
```
The [structural matrix](/ultra/verify) must pass at all widths before any
pixel work; there is no point pixel-matching a page that overflows at 390.
```bash theme={null}
eu-studio pixeldiff --page-url / --reference ref-1512.png \
--exclude 4210-4460:live-form-replaces-static-screenshot
```
Read the score against the bands (3 or less near-identical, 3 to 8
faithful, 8 to 20 visible, over 20 structural), fix the worst band first,
and re-run. **Mask mandated deviations** with `--exclude y0-y1:why`; a
deviation you chose (a real form where the reference shows a modal, a
skipped marquee) otherwise poisons the mean and makes the target
unreachable. Patch paddings and margins numerically from the deltas
(`eu-studio verify --anchors` gives per-anchor numbers); do not eyeball.
Fix rounds are budgeted, typically two or three. After the budget, report
residual deviations with their scores instead of chasing them. A faithful
build with a named 5px residue ships; an unbounded convergence loop does
not.
## Why this order
Copy before layout, because copy determines wraps. Assets and fonts before
styling, because a fallback font invalidates every measurement. Structure
before pixels, because overflow fixes change geometry wholesale. Masks before
score-chasing, because an unmasked mandated deviation makes the score
mathematically unreachable. Each rule in this recipe was paid for by a run that
did it in the other order.
## Related
All seven measurement modes and the pixeldiff score.
Turning this flow into a one-shot agent brief.
# Styling
Source: https://docs.wpos.ai/ultra/styling
The styling ladder: the tw Tailwind subset, the sx prop vocabulary, and the raw CSS escape hatch.
elementor-jsx styles compile to Elementor's atomic style envelopes, validated at
build time. Invalid values throw with the fix in the error message instead of
silently rendering wrong. There are three layers; always reach for them in this
order.
## The styling ladder
1. **`tw="..."`** for anything the Tailwind subset covers: layout, spacing, type
scale, radii, shadows. Shortest and most reviewable, and models emit it
natively.
2. **sx props** for what tw cannot express: partial sides, gradients, computed
values, theme tokens. `pad={{t: 64}}`, `grad={[160, '#111', '#333']}`,
`size={47}`.
3. **`raw="..."`** last, and only for genuinely non-atomic CSS: nested selectors
(`& em {...}`), pseudo-classes, filters, absolute-position offsets.
4. Explicit sx props win over tw on conflict; do not set both for the same
property.
## The tw subset
Desktop-first, mirroring sx: base = desktop, `max-lg:` = tablet, `max-md:` =
mobile. Spacing scale is n x 4px (`p-6` = 24px). Arbitrary values work:
`p-[96px]`, `w-[50%]`, `text-[15px]`, `bg-[#0A2230]`, `max-w-[960px]`.
```jsx theme={null}
```
Unknown utilities **throw at compile time**, naming the token: a silently
dropped class is a visual bug only a screenshot catches. Three deliberate gaps
get targeted errors with the working recipe:
* **Palette names** (`bg-blue-500`): use a literal or a theme token.
* **`hover:` and other state prefixes**: state styling goes through
`raw="&:hover {...}"`.
* **Mobile-first prefixes** (`md:`, `lg:`): the subset is desktop-first; use
`max-lg:` and `max-md:`.
## sx props
The full key list is on the [API card](/ultra/api-card). The rules that matter:
### Sizes and units
`w h maxw minh gap size radius pad m`: a bare number means px, or pass a unit
string with `px`, `%`, `em`, `rem`, `vw`, `vh`, or `ch`. Units are honored
(`maxw="88vw"` really is 88vw). `w` and `h` also take `'hug'` and `'auto'`.
Anything else, including `calc()`, `clamp()`, keywords, and two-value gap,
**throws at build time**. Put those in `raw=`.
`pad` and `m` also take `[v, h]`, `[t, r, b, l]`, a partial `{t, r, b, l}`
object, and `'0 auto'` strings.
### Alignment
`align` and `justify` are validated against Elementor's enums at build time:
* `justify`: `center`, `start`, `end`, `flex-start`, `flex-end`,
`space-between`, `space-around`, `space-evenly`, `stretch`. The shorthands
`'between'`, `'around'`, `'evenly'` auto-map.
* `align`: the same minus the `space-*` values, plus `self-start` and
`self-end`. There is **no baseline**; use `flex-end`.
* `dir` takes `'row'` or `'column'` (`'col'` throws).
### Color and background
* `color` and `bg` take hex values, **not** gradient strings.
* `grad` takes an array: `grad={[135, '#0ff', '#f0f']}` as angle, from, to. A
CSS gradient string throws. Freeform gradients go in
`raw="background-image: ...;"`.
* `bgImage` takes a URL or an attachment id; `bgOpts` takes
`{color, size: 'cover', position: 'center center', repeat: 'no-repeat', attachment}`.
### Typography
`size weight font lh ls ta` cover the type system. Bare-number `lh` (up to 4)
and `ls` are read as **em**, so `ls={-1}` collapses a headline to unreadable.
Explicit units are honored: `lh="150%"`, `ls="2px"`.
### Responsive
`tablet={{...}}` and `mobile={{...}}` take the same sx keys as breakpoint
overrides:
```jsx theme={null}
Headline
```
## raw: the escape hatch
`raw=""` takes CSS declarations (auto-terminated) and supports nested selectors
and pseudo-classes:
```jsx theme={null}
```
Raw compiles to `custom_css`, which only renders through global classes (or
the `--inline` build flag). Atomic tw/sx props always render. This is why the
ladder exists, and why `exjsx lint` flags raw declarations that atomic props
already cover (`raw-atomic-overlap`) and oversized raw blocks
(`oversized-raw`).
One sharp edge: class-only `display: grid` loses to Elementor's atomic flex CSS
printed later in the body. Set `display` and grid props via sx (atomic), and
keep only the extras (auto-rows, dense, bleed) in raw.
## Semantic class names: cls and gcls
* `cls="name"` labels the element's deduped shared class, so the Elementor
Class Manager shows `card` instead of `c-1x9fq2`. Give any pattern used three
or more times a `cls` (lint: `unnamed-shared-class`).
* `gcls="name"` attaches an arbitrary extra class you style yourself (in raw or
an html carrier).
## Next
What you can put these styles on.
The complete sx key list on one page.
# Troubleshooting
Source: https://docs.wpos.ai/ultra/troubleshooting
The field-learned gotcha library: symptom, cause, fix. Every entry cost a real debug cycle once.
Each entry below follows the same shape: what you see, why it happens, what
fixes it. The [verification gates](/ultra/verify) catch most of these
mechanically; this page is for when you are staring at the symptom.
## Layout
### The header links rail is centered instead of right-pinned
**Symptom:** in a `justify="space-between"` header, the links cluster sits
centered. Invisible at 1200, glaring at 1512 and wider.
**Cause:** a hidden-on-desktop mobile burger that hides its **button** but not
its **widget**. A bare `` widget as the third child still occupies the
right flex slot even when its inner button is `display: none`, so the row
balances against a ghost. This broke 8 of 10 sites in one batch run, and it is
exactly what the `empty-flex-slot` probe in `eu-studio check` fails on.
**Fix:** wrap the burger widget in a box that hides the whole widget on
desktop:
```jsx theme={null}
```
Or use `navBar()`, which is immune by construction. The same ghost-slot logic
applies to any html widget whose only content is hidden or absolutely
positioned, sitting in a centered or space-between flex row: the slot
participates in layout even though nothing paints.
### A heading shrinks and overflows on mobile
**Symptom:** a headline inside a flex column renders at max-content width and
pushes past 390px.
**Cause:** headings in flex columns shrink to max-content unless given width.
**Fix:** `w="100%"` on headings inside columns.
### An absolute overlay renders at zero size
**Symptom:** `pos: 'absolute'` plus `raw="inset:0;"` and the overlay is
invisible.
**Cause:** in a flex parent the overlay has no width or height of its own;
inset alone does not size it.
**Fix:** give the overlay explicit `w="100%" h="100%"` and the parent
`pos: 'relative'`.
### A fixed overlay is trapped inside the nav
**Symptom:** a `position: fixed` mobile menu positions itself relative to the
header instead of the viewport.
**Cause:** `backdrop-filter` on the nav creates a containing block for fixed
descendants.
**Fix:** toggle the filter off while the overlay is open:
`.open { backdrop-filter: none }`.
### A marquee or bleed decoration widens the page
**Symptom:** `check` fails at some width with an overflow offender that is a
decorative strip or marquee.
**Cause:** the decoration legitimately extends past the section edge.
**Fix:** `raw="overflow:hidden;"` on the section that contains it.
### Text never wraps on mobile
**Symptom:** a mono or nowrap line overflows 390px.
**Cause:** `white-space: nowrap` (and mono blocks styled that way) wrap
nowhere.
**Fix:** cap the width or allow wrapping below the tablet breakpoint.
## CSS and deploy state
### The page loads but looks unstyled or broken
**Symptom:** content renders, styling is partly or fully missing.
**Cause:** one of several CSS lifecycle failures: a stylesheet that 404s (or
301-redirects to the homepage and "serves" as HTML), an unprimed atomic page,
or a stale cache.
**Fix:** run `eu-studio doctor`. It checks every stylesheet the page enqueues
(asserting content-type, not just status), names the failing ones, and heals
via cache regen plus re-prime with retries. Do not hand-grep page HTML; the
doctor is faster and it is right.
### The page renders collapsed, sections stacked at a fraction of their height
**Symptom:** a previously fine page suddenly renders compressed or z-stacked;
`check` may fail `css-coverage`, naming classes no stylesheet defines.
**Cause:** the global class store lost classes the page references. The known
trigger is an Elementor editor session writing back an incomplete store copy.
On real sites the [companion plugin](/ultra/plugin) blocks the worst case (the
mass-deletion 409) and keeps a restorable backup; on the playground the store
can still lose classes.
**Fix:** redeploy from source (`exjsx deploy`, idempotent), which rewrites the
store; or on a plugin-guarded site, `POST
/elementor-ultra/v1/design/classes/restore`. Then `eu-studio doctor` to
confirm.
### Styles keep flaking on the playground specifically
**Symptom:** doctor heals CSS, it breaks again later, repeatedly.
**Cause:** the WASM playground's css file store is the flaky part, not your
build.
**Fix:** `eu-studio carry-css --page ` once at the end of the build: the
page's generated CSS is baked in as an inline carrier and survives the file
store. See also the CSS harvest recipe in the eu-studio README for the
last-resort path.
## Design review catches
### Dark theme text is unreadable on some screens
**Symptom:** body copy that looked fine in the editor is muddy on a real
display.
**Cause:** low-contrast ink on near-black backgrounds; hex values lie about
perceived contrast.
**Fix:** keep body text at roughly `#b0b8c0` or lighter on near-black, and
judge contrast in screenshots (`eu-studio measure shots`), not by hex.
### Fonts measure wrong everywhere
**Symptom:** every geometry measurement is slightly off; line wraps differ from
the design.
**Cause:** a family referenced only inside raw html widgets or `raw=` CSS is
invisible to Elementor's font enqueue and silently falls back.
**Fix:** name the family in a style prop somewhere, or add
`fontLoader('Family', [400, 700])` first in the tree. Confirm with
`eu-studio measure fonts`, which reports which families actually painted.
`exjsx lint` warns about this (`font-not-loaded`).
## When a deploy is refused
* **A 422 naming an envelope:** the server-side validator is authoritative; fix
the named envelope. The compiler's build-time gates catch almost all of these
first.
* **`skipped-drifted` pages:** someone hand-edited the page in Elementor.
Reconcile or consciously `--force`; see [Deploy](/ultra/deploy).
* **A class-store refusal telling you another project lives here:** you are
deploying a side bundle to an occupied site; the merge guard is protecting
the resident project. See [Deploy](/ultra/deploy).
Most of this page is encoded in the gates: `exjsx lint` catches the authoring
mistakes at build time, `eu-studio check` catches the layout failures in one
call, and `doctor` diagnoses the CSS lifecycle. Run the gates first; read
this page when a symptom survives them.
# Verify
Source: https://docs.wpos.ai/ultra/verify
The post-deploy gate: the structural matrix, interaction tests, the health doctor, and carry-css.
Verification is the half of the workflow agents historically reinvented per
session with hand-written Playwright. `eu-studio` packages it as one-call gates.
Never hand-write browser scripts for these.
Requirements: Playwright (`npm i playwright`, or set `EXJSX_IT_PLAYWRIGHT` to an
existing install's entry file).
## check: the entire gate in one call
```text theme={null}
usage: eu-studio check --pages /,/about/ [--widths 1200,1920,390] [--form /contact/] [--accordion /pricing/] [--burger /] [--nav /=about]
The ENTIRE post-deploy gate in one call and one browser session: structural matrix + interaction tests.
```
Running verify and clicktest separately costs three browser launches and two
model round-trips in the tail of every build; `check` shares a single Chromium
session. Real output against a production build:
```text theme={null}
✓ / @1200: sw=1200 h1=1
✓ / @1920: sw=1920 h1=1
✓ / @390: sw=390 h1=1
{"pass":true,"structural":{"pass":true,"results":[{"page":"/","width":1200,"ok":true,
"scrollWidth":1200,"h1":1,"height":8292,"offenders":[],"consoleErrors":[]},...]},
"interactions":{"pass":true,"results":[]}}
```
After a fix that touches one page, re-check only it
(`eu-studio check --pages /pricing/`); do not re-run the full matrix for a
single-page change.
### The structural matrix
Every page at every width (default `1200,1920,390`), scroll-settled and with
animations finished. Per combination it asserts:
* `scrollWidth` equals the viewport width, with overflow **offenders named** by
selector when it does not,
* exactly one `h1`,
* zero console page errors,
* and it reports body height.
Two **forensic probes fail the gate**. Both were added after a ten-site audit
where every defective site passed the basic checks:
* `empty-flex-slot`: a `space-between`/`around`/`evenly` row balancing against
an invisible child (the zero-width burger-wrapper signature). The geometry is
legal, but the page looks broken to a human. Intentional spacers
(`flex-grow > 0`) and `aria-hidden` separators with real width are exempt.
* `css-coverage`: every `c-*` class the DOM uses must be defined by some served
rule. A page can serve 200 with N stylesheets while its own post CSS or
carrier went stale; this probe catches the "748px z-stack while the gate
reports green" incident class. Runs once per page (width-independent).
Two probes **warn without failing** (some designs run legitimately narrow or
rotated):
* `min-content-collapse`: a squeezed column rendering one word per line never
overflows; the layout gave way instead of the content.
* `text-escapes-rotated-card`: text bleeding out of a transformed card never
trips `scrollWidth`; measured via text ink rectangles.
### Interaction tests
```text theme={null}
usage: eu-studio clicktest [--form /contact/] [--accordion /pricing/] [--burger /] [--nav /=about]
One call runs the standard interaction tests (no hand-written playwright needed):
--form fill visible inputs, submit, assert success (ajax success:true | success text | form hidden)
--accordion first : opens, body visible, closes back
--burger @390px: menu toggle reveals ≥2 links
--nav = click nav link whose text OR href pathname matches , assert navigation
```
The `--nav` matcher accepts either the link's visible text or its href pathname,
so `--nav /=about` passes whether the link says "About us" or points at
`/about/`. All four can be passed to `check` directly.
## verify: anchor deltas against a design
For pixel-faithful rebuilds with a reference design, `verify` measures where
your sections actually land:
```text theme={null}
usage: eu-studio verify --page-url --anchors anchors.json [--no-mobile] [--tolerance 2]
anchors.json shape:
{ "viewport": 1200, "totalHeight": 4081,
"anchors": [ { "name": "hero-h1", "selector": "h1", "y": 255 },
{ "name": "signup", "selector": "text=Get instant access", "y": 3660 } ],
"bands": [ { "name": "ticker", "y0": 930, "y1": 975, "minInkPx": 500 } ] }
```
* `selector` is a CSS selector or `text=`; `y` targets are
design-absolute px, pass/fail at `--tolerance` (default 2px).
* `bands` assert that a horizontal strip actually **paints** ink: DOM probes
cannot see paint, so black-ink assets on black sections pass every geometric
check while rendering invisible.
* Mobile: asserts `scrollWidth == 390` and names the overflowing elements.
## doctor: health checks plus self-heal
```bash theme={null}
eu-studio doctor [--page ] [--no-heal] [--json]
```
Checks, in order: `site` (frontend answers 200), `auth` (app-password REST auth
works), `seam` (companion plugin capabilities endpoint), `store` (every global
class a page references exists in the class store), `css` (every stylesheet the
page enqueues actually serves **as CSS**; a followed 200 that comes back
`text/html` is a redirected miss, not a healthy sheet). Real output:
```text theme={null}
✓ site: GET / -> 200
✓ auth: authenticated as admin
✓ seam: Elementor 4.2.1 + Pro 4.1.0, atomic on
✓ store: class store intact (123 classes, 2 page(s) checked)
✓ css:page-15: 5 stylesheet(s) serving
```
On a css failure, doctor heals: cache regen plus prime-css with retries, then a
re-check. On a **store** failure it can only detect, not invent: classes a page
references are gone from the store, and the fix is redeploying the bundle
(`exjsx deploy`), which doctor tells you. When styles look broken, run doctor
instead of hand-grepping page HTML; it names the failing stylesheets.
## carry-css: independence from a flaky file store
```text theme={null}
usage: eu-studio carry-css --page [--page ...]
Bakes each page's generated CSS into the page as an inline carrier : styling survives the flaky
Playground css file store. Idempotent.
```
A [playground](/ultra/playground)-specific last resort: if the WASM file store
keeps flaking after doctor heals, run carry-css once at the end of a build.
## Which tool when
| Situation | Tool |
| - | - |
| After every deploy | `check --pages ...` (plus interaction flags where relevant) |
| Rebuilding against a design reference | `verify --anchors`, then [pixeldiff](/ultra/measure) |
| Styles look broken | `doctor` |
| Playground css store keeps flaking | `carry-css`, once, at the end |