From redaxo-yform
Exposes YForm tables as JSON:API REST endpoints via PHP routes, with GET filtering/pagination/sorting, POST/DELETE CRUD, token auth, and field whitelists. For configuring YForm-backed APIs serving SPAs.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-yformThis skill uses the workspace's default tool permissions.
YForm includes a REST layer that exposes a YForm-backed dataset class as JSON:API endpoints. Authentication is token-based; routes are versioned by path; per-method field whitelists control what's readable / writable.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Builds production-ready Apache Airflow DAGs with patterns for operators, sensors, testing, and deployment. For data pipelines, workflow orchestration, and batch jobs.
Share bugs, ideas, or general feedback.
YForm includes a REST layer that exposes a YForm-backed dataset class as JSON:API endpoints. Authentication is token-based; routes are versioned by path; per-method field whitelists control what's readable / writable.
In your project addon's boot.php:
$route = new \rex_yform_rest_route([
'path' => '/v1/articles/',
'auth' => '\rex_yform_rest_auth_token::checkToken',
'type' => MyArticle::class,
'query' => MyArticle::query(),
'get' => [
MyArticle::class => [
'fields' => ['id', 'title', 'text', 'status'],
],
],
'post' => [
MyArticle::class => [
'fields' => ['title', 'text', 'status'],
],
],
'delete' => [
MyArticle::class => [
'fields' => ['id'],
],
],
]);
\rex_yform_rest::addRoute($route);
MyArticle must be a rex_yform_manager_dataset subclass registered with setModelClass(). The query value gives you a starting point — add where() clauses there to scope what the endpoint exposes.
All routes live under /rest/. With the example above, the endpoint URL is:
GET /rest/v1/articles/
POST /rest/v1/articles/
DELETE /rest/v1/articles/?filter[id]=5
| Parameter | Description | Example |
|---|---|---|
filter[field]=value | Filter records | ?filter[status]=1 |
include=relation | Include related dataset(s) | ?include=category |
per_page=N | Items per page | ?per_page=10 |
page=N | Page number | ?page=2 |
order[field]=dir | Sort | ?order[created]=desc |
Multiple filters are AND-combined: ?filter[status]=1&filter[clang_id]=1.
{
"data": {
"type": "MyArticle",
"attributes": {
"title": "Neuer Artikel",
"text": "Inhalt...",
"status": 1
}
}
}
The type value must match the class name registered in the route (MyArticle::class). Only fields listed in post.fields are accepted; everything else is ignored.
DELETE /rest/v1/articles/?filter[id]=5
Deletes match the same filter[...] semantics as GET. Be careful with broad filters — the API will happily delete every matching record.
Default is token-based via \rex_yform_rest_auth_token::checkToken. Manage tokens in the backend under YForm → REST → Tokens. Token scopes can be limited to specific routes.
Header:
Authorization: Bearer <token>
For public read-only endpoints, set 'auth' => null — but think hard before doing this; an open POST is an open data-injection target.
Global (applies to every YForm REST route):
\rex_yform_rest::setHeader('Access-Control-Allow-Origin', '*');
Per-route:
$route->setHeader('Access-Control-Allow-Origin', 'https://example.com');
For browser-based SPAs, add:
$route->setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
$route->setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
get.fields, post.fields, delete.fields are independent. Practical pattern:
get.fields – everything safe to expose (excluding internal admin notes, soft-delete flags)post.fields – the user-editable subset (excluding id, created_at, status if those are server-controlled)delete.fields – usually just ['id'] for filter purposesAuthorization headerApache strips Authorization by default in some configurations. If valid tokens get rejected with Authorization failed, add to .htaccess:
RewriteCond %{HTTP:Authorization} .
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
boot.php that fires too late – register in boot.php directly (top level), not inside a callback that runs only on backend requests.setModelClass() – the route serializes plain rex_yform_manager_dataset instead of your subclass, and type mismatches break POST.'auth' => null for "just a quick test" and forgetting to restore it before deploying.post.fields that don't exist on the table – the field gets silently dropped from incoming POSTs.OPTIONS requests fail with no clear error.query start un-scoped – an open MyArticle::query() exposes every row in the table, including drafts and soft-deleted records. Always add baseline where() filters.