oneie
Version:
Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.
1,321 lines (1,115 loc) • 39.9 kB
Markdown
---
title: Wordpress
dimension: connections
category: wordpress.md
tags: architecture, backend, frontend, ontology
related_dimensions: events, knowledge, people, things
scope: global
created: 2025-11-03
updated: 2025-11-03
version: 1.0.0
ai_context: |
This document is part of the connections dimension in the wordpress.md category.
Location: one/connections/wordpress.md
Purpose: Documents connecting one to wordpress backend
Related dimensions: events, knowledge, people, things
For AI agents: Read this to understand wordpress.
---
# Connecting ONE to WordPress Backend
**Using WordPress + WooCommerce as your ONE backend**
---
## Executive Summary
This guide shows you how to connect the ONE platform frontend to a **WordPress + WooCommerce backend**, turning WordPress into a full-featured backend for the ONE ontology.
**What You Get:**
- ✅ Use existing WordPress infrastructure
- ✅ Leverage WordPress posts, pages, users, custom post types
- ✅ WooCommerce for products and e-commerce
- ✅ WordPress plugins ecosystem
- ✅ Familiar WordPress admin interface
- ✅ WordPress REST API as backend
**Architecture:**
```
┌────────────────────────────────────────────────┐
│ ONE Frontend (Astro + React) │
│ - Renders UI │
│ - Calls DataProvider interface │
│ - Backend-agnostic │
└────────────────┬───────────────────────────────┘
│ DataProvider Interface
↓
┌────────────────────────────────────────────────┐
│ WordPressProvider │
│ - Implements DataProvider interface │
│ - Translates ONE ontology → WordPress API │
│ - Maps things → posts/pages/products │
│ - Maps connections → relationships │
└────────────────┬───────────────────────────────┘
│ WordPress REST API
↓
┌────────────────────────────────────────────────┐
│ WordPress Backend │
│ - wp_posts (posts, pages, products) │
│ - wp_postmeta (properties) │
│ - wp_users (people) │
│ - wp_term_relationships (connections) │
│ - wp_comments (events) │
│ - WooCommerce tables (orders, products) │
└────────────────────────────────────────────────┘
```
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [WordPress Setup](#wordpress-setup)
3. [Authentication](#authentication)
4. [Ontology Mapping](#ontology-mapping)
5. [WordPressProvider Implementation](#wordpressprovider-implementation)
6. [Configuration](#configuration)
7. [Usage Examples](#usage-examples)
8. [WooCommerce Integration](#woocommerce-integration)
9. [Custom Post Types](#custom-post-types)
10. [Limitations and Workarounds](#limitations-and-workarounds)
11. [Deployment](#deployment)
---
## Prerequisites
**WordPress Requirements:**
- WordPress 5.0+ (REST API built-in)
- PHP 7.4+
- WooCommerce 5.0+ (optional, for e-commerce)
- Application Passwords enabled (WP 5.6+)
**Recommended Plugins:**
- **WooCommerce** - E-commerce functionality
- **Advanced Custom Fields (ACF)** - Extend post meta (optional)
- **Custom Post Type UI** - Create custom post types (optional)
- **JWT Authentication for WP REST API** - Better auth (optional)
**Frontend Requirements:**
- Node.js 18+
- ONE frontend setup (see ontology-frontend.md)
---
## WordPress Setup
### 1. Enable REST API
WordPress REST API is enabled by default in WordPress 5.0+. Test it:
```bash
# Test your WordPress REST API
curl https://yoursite.com/wp-json/wp/v2/posts
```
### 2. Enable Application Passwords
**In WordPress Admin:**
1. Go to **Users → Your Profile**
2. Scroll to **Application Passwords**
3. Enter name: "ONE Platform"
4. Click **Add New Application Password**
5. **Copy the generated password** (save it securely)
### 3. Install WooCommerce (Optional)
```bash
# Via WordPress Admin
Plugins → Add New → Search "WooCommerce" → Install → Activate
# Or via WP-CLI
wp plugin install woocommerce --activate
```
### 4. Configure Permalinks
**Ensure pretty permalinks are enabled:**
WordPress Admin → Settings → Permalinks → Select **Post name**
---
## Authentication
### Method 1: Application Passwords (Recommended)
```typescript
// frontend/.env
WORDPRESS_URL=https://yoursite.com
WORDPRESS_USERNAME=your_username
WORDPRESS_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
```
```typescript
// frontend/src/providers/wordpress/auth.ts
export function createAuthHeaders(username: string, password: string) {
const credentials = btoa(`${username}:${password}`)
return {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
}
}
```
### Method 2: JWT Authentication (More Secure)
**Install JWT Plugin:**
```bash
wp plugin install jwt-authentication-for-wp-rest-api --activate
```
**Configure wp-config.php:**
```php
// wp-config.php
define('JWT_AUTH_SECRET_KEY', 'your-secret-key-here');
define('JWT_AUTH_CORS_ENABLE', true);
```
**.htaccess:**
```apache
# Add to .htaccess
RewriteEngine on
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
```
**Get JWT Token:**
```typescript
// frontend/src/providers/wordpress/auth.ts
import { Effect } from 'effect'
export function getJWTToken(username: string, password: string) {
return Effect.tryPromise({
try: async () => {
const response = await fetch('https://yoursite.com/wp-json/jwt-auth/v1/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
const data = await response.json()
return data.token
},
catch: (error) => new Error(String(error))
})
}
export function createJWTHeaders(token: string) {
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
```
---
## Ontology Mapping
**How ONE ontology maps to WordPress:**
### Things → WordPress Content Types
| ONE Thing Type | WordPress Type | REST Endpoint | Notes |
|----------------|----------------|---------------|-------|
| `post` | Post | `/wp/v2/posts` | Blog posts |
| `page` | Page | `/wp/v2/pages` | Static pages |
| `person` | User | `/wp/v2/users` | WordPress users |
| `product` | WooCommerce Product | `/wc/v3/products` | Requires WooCommerce |
| `course` | Custom Post Type | `/wp/v2/course` | Create custom type |
| `lesson` | Custom Post Type | `/wp/v2/lesson` | Create custom type |
| `event` | Custom Post Type | `/wp/v2/event` | Create custom type |
| `organization` | Custom Post Type | `/wp/v2/organization` | Create custom type |
### Connections → WordPress Relationships
| ONE Connection | WordPress Implementation | Notes |
|----------------|-------------------------|-------|
| `created_by` | Post author | `post.author` field |
| `part_of` | Post parent | `post.parent` field |
| `tagged_with` | Tags | `post.tags` array |
| `categorized_as` | Categories | `post.categories` array |
| `enrolled_in` | User meta | Custom meta: `user_meta.enrolled_courses` |
| `purchased` | WooCommerce orders | `order.line_items` |
### Events → WordPress Activity
| ONE Event Type | WordPress Implementation | Notes |
|----------------|-------------------------|-------|
| `thing_created` | Post published | Hook: `publish_post` |
| `thing_updated` | Post modified | Hook: `save_post` |
| `thing_viewed` | WP Statistics | Plugin or custom table |
| `comment_added` | Comments | `wp_comments` table |
| `user_login` | User login | Hook: `wp_login` |
### Knowledge → WordPress Search
| ONE Feature | WordPress Implementation | Notes |
|-------------|-------------------------|-------|
| Embeddings | External service | Use Pinecone/Weaviate |
| Search | WordPress search | `/wp/v2/search` endpoint |
| Vector search | ElasticSearch | Plugin integration |
---
## WordPressProvider Implementation
### Core Provider Class
```typescript
// frontend/src/providers/wordpress/WordPressProvider.ts
import { Effect, Layer } from 'effect'
import { DataProvider, ThingNotFoundError, ConnectionCreateError } from '../DataProvider'
import { createAuthHeaders } from './auth'
export class WordPressProvider implements DataProvider {
private headers: Record<string, string>
constructor(
private baseUrl: string,
private username: string,
private password: string
) {
this.headers = createAuthHeaders(username, password)
}
things = {
get: (id: string) =>
Effect.gen(this, function* () {
// Determine post type from ID or fetch generically
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${id}`, {
headers: this.headers
}),
catch: (error) => new Error(String(error))
})
if (!response.ok) {
if (response.status === 404) {
return yield* Effect.fail(new ThingNotFoundError(id))
}
return yield* Effect.fail(new Error(`HTTP ${response.status}`))
}
const post = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
// Transform WordPress post → ONE thing
return {
_id: post.id.toString(),
type: this.mapPostTypeToThingType(post.type),
name: post.title.rendered,
properties: {
content: post.content.rendered,
excerpt: post.excerpt.rendered,
slug: post.slug,
status: post.status,
author: post.author.toString(),
featuredImage: post.featured_media,
categories: post.categories,
tags: post.tags,
// Custom fields from ACF or post meta
...post.meta
},
status: post.status === 'publish' ? 'active' : 'inactive',
createdAt: new Date(post.date).getTime(),
updatedAt: new Date(post.modified).getTime(),
createdBy: post.author.toString()
}
}),
list: (params) =>
Effect.gen(this, function* () {
const endpoint = this.getEndpointForType(params.type)
const query = new URLSearchParams({
per_page: String(params.limit || 10),
page: String(params.page || 1),
status: 'publish'
})
// Add filters
if (params.filters?.author) {
query.append('author', params.filters.author)
}
if (params.filters?.category) {
query.append('categories', params.filters.category)
}
if (params.filters?.search) {
query.append('search', params.filters.search)
}
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/${endpoint}?${query}`, {
headers: this.headers
}),
catch: (error) => new Error(String(error))
})
const posts = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
// Transform WordPress posts → ONE things
return posts.map((post: any) => ({
_id: post.id.toString(),
type: params.type,
name: post.title.rendered,
properties: {
excerpt: post.excerpt.rendered,
slug: post.slug,
featuredImage: post.featured_media
},
status: post.status === 'publish' ? 'active' : 'inactive',
createdAt: new Date(post.date).getTime(),
updatedAt: new Date(post.modified).getTime()
}))
}),
create: (input) =>
Effect.gen(this, function* () {
const endpoint = this.getEndpointForType(input.type)
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/${endpoint}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
title: input.name,
content: input.properties.content || '',
excerpt: input.properties.excerpt || '',
status: 'draft',
meta: input.properties
})
}),
catch: (error) => new ConnectionCreateError(String(error))
})
if (!response.ok) {
return yield* Effect.fail(
new ConnectionCreateError(`Failed to create: ${response.status}`)
)
}
const post = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new ConnectionCreateError(String(error))
})
return post.id.toString()
}),
update: (id, updates) =>
Effect.gen(this, function* () {
const endpoint = this.getEndpointForType(updates.type || 'post')
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/${endpoint}/${id}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
title: updates.name,
content: updates.properties?.content,
status: updates.status === 'active' ? 'publish' : 'draft',
meta: updates.properties
})
}),
catch: (error) => new Error(String(error))
})
}),
delete: (id) =>
Effect.gen(this, function* () {
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${id}`, {
method: 'DELETE',
headers: this.headers,
body: JSON.stringify({ force: true })
}),
catch: (error) => new Error(String(error))
})
})
}
connections = {
create: (input) =>
Effect.gen(this, function* () {
// WordPress connections via relationships
switch (input.relationshipType) {
case 'part_of':
// Use post parent
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${input.fromThingId}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
parent: parseInt(input.toThingId)
})
}),
catch: (error) => new ConnectionCreateError(String(error))
})
return `${input.fromThingId}-${input.toThingId}-parent`
case 'tagged_with':
// Use tags
const post = yield* this.things.get(input.fromThingId)
const currentTags = post.properties.tags || []
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${input.fromThingId}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
tags: [...currentTags, parseInt(input.toThingId)]
})
}),
catch: (error) => new ConnectionCreateError(String(error))
})
return `${input.fromThingId}-${input.toThingId}-tag`
case 'enrolled_in':
// Use user meta
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/users/${input.fromThingId}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
meta: {
enrolled_courses: [input.toThingId]
}
})
}),
catch: (error) => new ConnectionCreateError(String(error))
})
return `${input.fromThingId}-${input.toThingId}-enrollment`
default:
// Store in custom table or post meta
return yield* this.createCustomConnection(input)
}
}),
getRelated: (params) =>
Effect.gen(this, function* () {
switch (params.relationshipType) {
case 'part_of':
// Get children via parent query
const response = yield* Effect.tryPromise({
try: () =>
fetch(
`${this.baseUrl}/wp-json/wp/v2/posts?parent=${params.thingId}`,
{ headers: this.headers }
),
catch: (error) => new Error(String(error))
})
const children = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return children.map((post: any) => this.transformPostToThing(post))
case 'tagged_with':
// Get posts by tag
const tagResponse = yield* Effect.tryPromise({
try: () =>
fetch(
`${this.baseUrl}/wp-json/wp/v2/posts?tags=${params.thingId}`,
{ headers: this.headers }
),
catch: (error) => new Error(String(error))
})
const posts = yield* Effect.tryPromise({
try: () => tagResponse.json(),
catch: (error) => new Error(String(error))
})
return posts.map((post: any) => this.transformPostToThing(post))
default:
// Query custom connections table
return []
}
}),
getCount: (thingId, relationshipType) =>
Effect.gen(this, function* () {
const related = yield* this.connections.getRelated({
thingId,
relationshipType,
direction: 'both'
})
return related.length
}),
delete: (id) =>
Effect.gen(this, function* () {
// Delete connection (implementation depends on storage method)
// For parent: update post to remove parent
// For tags: remove tag from post
// For user meta: remove from meta array
})
}
events = {
log: (event) =>
Effect.gen(this, function* () {
// Option 1: Use WordPress comments as event log
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/comments`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
post: event.targetId,
author: event.actorId,
content: JSON.stringify({
type: event.type,
metadata: event.metadata
}),
meta: {
event_type: event.type,
event_metadata: event.metadata
}
})
}),
catch: (error) => new Error(String(error))
})
// Option 2: Custom REST endpoint with custom table
// Option 3: External logging service
}),
query: (params) =>
Effect.gen(this, function* () {
// Query comments table or custom events table
const query = new URLSearchParams()
if (params.actorId) query.append('author', params.actorId)
if (params.targetId) query.append('post', params.targetId)
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/comments?${query}`, {
headers: this.headers
}),
catch: (error) => new Error(String(error))
})
const comments = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return comments.map((comment: any) => ({
_id: comment.id.toString(),
type: comment.meta.event_type || 'comment_added',
actorId: comment.author.toString(),
targetId: comment.post?.toString(),
metadata: comment.meta.event_metadata || {},
timestamp: new Date(comment.date).getTime()
}))
})
}
knowledge = {
embed: (params) =>
Effect.gen(this, function* () {
// WordPress doesn't have native vector embeddings
// Option 1: Use external service (Pinecone, Weaviate)
// Option 2: ElasticSearch plugin
// Option 3: Store embeddings in post meta, search externally
// Example: Store in post meta for later external processing
yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${params.sourceThingId}`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
meta: {
embedding_text: params.text,
embedding_labels: params.labels
}
})
}),
catch: (error) => new Error(String(error))
})
return params.sourceThingId
}),
search: (query, limit = 10) =>
Effect.gen(this, function* () {
// Use WordPress built-in search
const response = yield* Effect.tryPromise({
try: () =>
fetch(
`${this.baseUrl}/wp-json/wp/v2/search?search=${encodeURIComponent(query)}&per_page=${limit}`,
{ headers: this.headers }
),
catch: (error) => new Error(String(error))
})
const results = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return results.map((result: any) => ({
thingId: result.id.toString(),
score: 0.9, // WordPress doesn't return relevance scores
text: result.title,
metadata: {
type: result.subtype,
url: result.url
}
}))
})
}
// Helper methods
private mapPostTypeToThingType(postType: string): string {
const mapping: Record<string, string> = {
'post': 'post',
'page': 'page',
'product': 'product',
'course': 'course',
'lesson': 'lesson'
}
return mapping[postType] || 'post'
}
private getEndpointForType(type: string): string {
const endpoints: Record<string, string> = {
'post': 'wp/v2/posts',
'page': 'wp/v2/pages',
'person': 'wp/v2/users',
'product': 'wc/v3/products',
'course': 'wp/v2/course',
'lesson': 'wp/v2/lesson'
}
return endpoints[type] || 'wp/v2/posts'
}
private transformPostToThing(post: any) {
return {
_id: post.id.toString(),
type: this.mapPostTypeToThingType(post.type),
name: post.title.rendered,
properties: {
content: post.content.rendered,
excerpt: post.excerpt.rendered
},
status: 'active',
createdAt: new Date(post.date).getTime(),
updatedAt: new Date(post.modified).getTime()
}
}
private createCustomConnection(input: any) {
// Implement custom connection storage
// Option 1: Post meta array
// Option 2: Custom database table
// Option 3: Taxonomy terms
return Effect.succeed(`${input.fromThingId}-${input.toThingId}`)
}
}
// Factory function
export function wordpressProvider(config: {
url: string
username: string
password: string
}) {
return Layer.succeed(
DataProvider,
new WordPressProvider(config.url, config.username, config.password)
)
}
```
---
## Configuration
### Astro Config
```typescript
// frontend/astro.config.ts
import { defineConfig } from 'astro/config'
import react from '@astrojs/react'
import { one } from '@one/astro-integration'
import { wordpressProvider } from './src/providers/wordpress'
export default defineConfig({
integrations: [
react(),
one({
// ✅ Use WordPress as backend
provider: wordpressProvider({
url: import.meta.env.WORDPRESS_URL,
username: import.meta.env.WORDPRESS_USERNAME,
password: import.meta.env.WORDPRESS_APP_PASSWORD
})
})
]
})
```
### Environment Variables
```bash
# frontend/.env
WORDPRESS_URL=https://yoursite.com
WORDPRESS_USERNAME=your_username
WORDPRESS_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
# Or with JWT
WORDPRESS_JWT_TOKEN=your_jwt_token_here
```
---
## Usage Examples
### Example 1: List WordPress Posts
```tsx
// frontend/src/components/BlogList.tsx
import { useEffectRunner } from '@/hooks/useEffectRunner'
import { ThingClientService } from '@/services/ThingClientService'
import { Effect } from 'effect'
import { useEffect, useState } from 'react'
export function BlogList() {
const { run, loading } = useEffectRunner()
const [posts, setPosts] = useState([])
useEffect(() => {
const program = Effect.gen(function* () {
const thingService = yield* ThingClientService
// Fetch posts from WordPress
return yield* thingService.list('post')
})
run(program, {
onSuccess: setPosts
})
}, [])
if (loading) return <div>Loading posts...</div>
return (
<div>
{posts.map(post => (
<article key={post._id}>
<h2>{post.name}</h2>
<div dangerouslySetInnerHTML={{ __html: post.properties.excerpt }} />
<a href={`/posts/${post._id}`}>Read more</a>
</article>
))}
</div>
)
}
```
### Example 2: Create WordPress Post
```tsx
// frontend/src/components/PostForm.tsx
import { useEffectRunner } from '@/hooks/useEffectRunner'
import { ThingClientService } from '@/services/ThingClientService'
import { Effect } from 'effect'
import { useState } from 'react'
export function PostForm() {
const { run, loading, error } = useEffectRunner()
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const program = Effect.gen(function* () {
const thingService = yield* ThingClientService
// Create post in WordPress
const postId = yield* thingService.create({
type: 'post',
name: title,
properties: {
content,
excerpt: content.substring(0, 150)
}
})
return postId
})
run(program, {
onSuccess: (id) => {
console.log('Post created:', id)
window.location.href = `/posts/${id}`
}
})
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Post Title"
required
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Post Content"
rows={10}
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Post'}
</button>
{error && <div className="error">{error}</div>}
</form>
)
}
```
---
## WooCommerce Integration
### WooCommerce Provider Extension
```typescript
// frontend/src/providers/wordpress/WooCommerceExtension.ts
import { Effect } from 'effect'
export class WooCommerceExtension {
constructor(
private baseUrl: string,
private consumerKey: string,
private consumerSecret: string
) {}
private get authParams() {
return `consumer_key=${this.consumerKey}&consumer_secret=${this.consumerSecret}`
}
products = {
list: (params: { limit?: number; category?: string }) =>
Effect.gen(this, function* () {
const query = new URLSearchParams({
per_page: String(params.limit || 10),
...(params.category && { category: params.category })
})
const response = yield* Effect.tryPromise({
try: () =>
fetch(
`${this.baseUrl}/wp-json/wc/v3/products?${query}&${this.authParams}`
),
catch: (error) => new Error(String(error))
})
const products = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return products.map((product: any) => ({
_id: product.id.toString(),
type: 'product',
name: product.name,
properties: {
description: product.description,
price: product.price,
regularPrice: product.regular_price,
salePrice: product.sale_price,
sku: product.sku,
stock: product.stock_quantity,
images: product.images.map((img: any) => img.src),
categories: product.categories.map((cat: any) => cat.name)
},
status: product.status === 'publish' ? 'active' : 'inactive',
createdAt: new Date(product.date_created).getTime(),
updatedAt: new Date(product.date_modified).getTime()
}))
}),
create: (input: {
name: string
price: number
description: string
sku?: string
}) =>
Effect.gen(this, function* () {
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wc/v3/products?${this.authParams}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: input.name,
type: 'simple',
regular_price: input.price.toString(),
description: input.description,
sku: input.sku
})
}),
catch: (error) => new Error(String(error))
})
const product = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return product.id.toString()
})
}
orders = {
list: (customerId?: string) =>
Effect.gen(this, function* () {
const query = customerId ? `customer=${customerId}&` : ''
const response = yield* Effect.tryPromise({
try: () =>
fetch(
`${this.baseUrl}/wp-json/wc/v3/orders?${query}${this.authParams}`
),
catch: (error) => new Error(String(error))
})
const orders = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return orders.map((order: any) => ({
_id: order.id.toString(),
type: 'order',
customerId: order.customer_id.toString(),
items: order.line_items.map((item: any) => ({
productId: item.product_id.toString(),
quantity: item.quantity,
price: item.price
})),
total: order.total,
status: order.status,
createdAt: new Date(order.date_created).getTime()
}))
}),
create: (input: {
customerId: string
items: Array<{ productId: string; quantity: number }>
}) =>
Effect.gen(this, function* () {
const response = yield* Effect.tryPromise({
try: () =>
fetch(`${this.baseUrl}/wp-json/wc/v3/orders?${this.authParams}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
customer_id: parseInt(input.customerId),
line_items: input.items.map(item => ({
product_id: parseInt(item.productId),
quantity: item.quantity
}))
})
}),
catch: (error) => new Error(String(error))
})
const order = yield* Effect.tryPromise({
try: () => response.json(),
catch: (error) => new Error(String(error))
})
return order.id.toString()
})
}
}
```
---
## Custom Post Types
### Create Custom Post Types for ONE
```php
// wp-content/themes/your-theme/functions.php
// Or create a custom plugin
function one_register_custom_post_types() {
// Course post type
register_post_type('course', [
'label' => 'Courses',
'public' => true,
'show_in_rest' => true, // ✅ REQUIRED for REST API
'rest_base' => 'course',
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
'has_archive' => true,
'rewrite' => ['slug' => 'courses']
]);
// Lesson post type
register_post_type('lesson', [
'label' => 'Lessons',
'public' => true,
'show_in_rest' => true,
'rest_base' => 'lesson',
'supports' => ['title', 'editor', 'custom-fields'],
'hierarchical' => true, // Allows parent/child
'rewrite' => ['slug' => 'lessons']
]);
// Event post type
register_post_type('event', [
'label' => 'Events',
'public' => true,
'show_in_rest' => true,
'rest_base' => 'event',
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
'rewrite' => ['slug' => 'events']
]);
// Organization post type
register_post_type('organization', [
'label' => 'Organizations',
'public' => true,
'show_in_rest' => true,
'rest_base' => 'organization',
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
'rewrite' => ['slug' => 'organizations']
]);
}
add_action('init', 'one_register_custom_post_types');
// Add custom fields support to REST API
function one_register_custom_fields() {
register_post_meta('course', 'price', [
'show_in_rest' => true,
'single' => true,
'type' => 'number'
]);
register_post_meta('course', 'duration', [
'show_in_rest' => true,
'single' => true,
'type' => 'string'
]);
register_post_meta('lesson', 'video_url', [
'show_in_rest' => true,
'single' => true,
'type' => 'string'
]);
}
add_action('init', 'one_register_custom_fields');
```
---
## Limitations and Workarounds
### WordPress Limitations
| Limitation | Workaround |
|------------|-----------|
| **No native graph relationships** | Use post parent, taxonomies, or custom tables |
| **No vector embeddings** | Integrate external service (Pinecone, Weaviate) |
| **No real-time subscriptions** | Poll API or use WordPress webhooks |
| **Limited query flexibility** | Create custom REST endpoints |
| **No multi-tenancy** | Use WordPress Multisite or organization taxonomy |
### Custom REST Endpoints
```php
// Add custom endpoint for complex queries
function one_register_custom_endpoints() {
register_rest_route('one/v1', '/courses/(?P<id>\d+)/lessons', [
'methods' => 'GET',
'callback' => 'one_get_course_lessons',
'permission_callback' => '__return_true'
]);
}
add_action('rest_api_init', 'one_register_custom_endpoints');
function one_get_course_lessons($request) {
$course_id = $request['id'];
$lessons = get_posts([
'post_type' => 'lesson',
'post_parent' => $course_id,
'posts_per_page' => -1
]);
return array_map(function($lesson) {
return [
'id' => $lesson->ID,
'title' => $lesson->post_title,
'content' => $lesson->post_content,
'video_url' => get_post_meta($lesson->ID, 'video_url', true)
];
}, $lessons);
}
```
---
## Deployment
### 1. Deploy WordPress
**Recommended Hosting:**
- **WP Engine** - Managed WordPress (premium)
- **Kinsta** - Managed WordPress (premium)
- **SiteGround** - Affordable, good performance
- **Digital Ocean** - VPS with WordPress droplet
- **AWS Lightsail** - WordPress instance ($10/mo)
### 2. Configure WordPress
```bash
# Install WordPress via WP-CLI
wp core install \
--url="https://yoursite.com" \
--title="ONE Backend" \
--admin_user="admin" \
--admin_email="admin@yoursite.com"
# Install required plugins
wp plugin install woocommerce --activate
wp plugin install jwt-authentication-for-wp-rest-api --activate
# Set up permalinks
wp rewrite structure '/%postname%/' --hard
# Create application password
wp user application-password create admin "ONE Platform"
```
### 3. Deploy Frontend
```bash
# Frontend (separate deployment)
cd frontend
npm run build
# Deploy to Cloudflare Pages / Vercel / Netlify
# Set environment variables in hosting platform
WORDPRESS_URL=https://yoursite.com
WORDPRESS_USERNAME=admin
WORDPRESS_APP_PASSWORD=xxxx xxxx xxxx xxxx
```
### 4. Configure CORS (if needed)
```php
// wp-content/themes/your-theme/functions.php
function one_add_cors_headers() {
header('Access-Control-Allow-Origin: https://your-frontend.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, Content-Type');
}
add_action('rest_api_init', 'one_add_cors_headers');
```
---
## Summary
### ✅ WordPress as ONE Backend: Pros
- **Familiar** - Use WordPress admin, existing knowledge
- **Plugins** - Thousands of WordPress plugins available
- **WooCommerce** - Built-in e-commerce functionality
- **Hosting** - Many affordable hosting options
- **Content Management** - Mature content editing tools
- **SEO** - WordPress SEO plugins (Yoast, Rank Math)
### ⚠️ WordPress as ONE Backend: Cons
- **No Real-Time** - Poll API or use webhooks
- **Limited Relationships** - Need workarounds for graph connections
- **Performance** - Slower than purpose-built backends (Convex, Supabase)
- **No Vector Search** - Requires external integration
- **PHP Required** - Need to write PHP for custom endpoints
- **Scaling** - WordPress can struggle at very large scale
### When to Use WordPress Backend
✅ **Good For:**
- Existing WordPress sites migrating to ONE
- Teams familiar with WordPress
- Organizations needing WordPress admin UI
- E-commerce sites using WooCommerce
- Content-heavy sites (blogs, magazines)
- Budget-conscious projects
❌ **Not Ideal For:**
- Real-time applications (use Convex or Supabase)
- Complex graph relationships (use Neo4j or Convex)
- High-performance requirements (use Convex)
- Vector search/AI features (use Pinecone + Convex)
---
## Next Steps
1. **Set up WordPress** with REST API and Application Passwords
2. **Install WooCommerce** (if doing e-commerce)
3. **Create custom post types** for your ONE thing types
4. **Implement WordPressProvider** in frontend
5. **Configure Astro** to use `wordpressProvider`
6. **Test CRUD operations** with WordPress REST API
7. **Add custom endpoints** for complex queries
8. **Deploy** WordPress backend and ONE frontend
---
## Resources
### WordPress REST API
- [WordPress REST API Handbook](https://developer.wordpress.org/rest-api/)
- [Authentication](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/)
- [Custom Post Types](https://developer.wordpress.org/plugins/post-types/)
### WooCommerce REST API
- [WooCommerce REST API Docs](https://woocommerce.github.io/woocommerce-rest-api-docs/)
- [Authentication](https://woocommerce.github.io/woocommerce-rest-api-docs/#authentication)
### Related ONE Guides
- **ontology-frontend.md** - Frontend implementation (this guide references)
- **ontology-backend.md** - Backend patterns
- **Ontology.md** - Complete ontology specification
---
**WordPress → ONE = Familiar CMS + Modern Frontend Architecture**