Mutations
Create, update, and delete entities from the server client (createPyloServer / createPyloNode)
or from Client Components with the hooks.
Server
Upsert
Create or update with the _search_value pattern. Pylo updates the record that matches the search,
or creates one when nothing matches.
1
2
3
4
5
6
7
8
9
10
11
const result = await pylo.invoice.upsert({
_search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1000,
});
// result: { id: string }With relations
Use _set to connect related entities during upsert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
await pylo.invoice.upsert({
_search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1500,
customer_set: {
_search_value: {
field: 'customer_number',
value: 'C-001',
not_found_behavior: 'create',
},
name: 'Acme Corp',
},
});Search values
_search_value targets the record by a field instead of an id. It works at the top level and
inside nested relation upserts (as in the example above). The older spelling __search_value is
deprecated and still accepted.
| Option | Default | Description |
|---|---|---|
field | required | Field to search by |
value | Value to match | |
not_found_behavior | 'create' | 'create' inserts a new record, 'error' fails, 'ignore' skips (nested relation upserts only) |
search_in_all_field_variants | false | Search across all variants of the field instead of only the requested ones |
multiple_results_allowed | false | Allow more than one match; without it, multiple matches fail the mutation |
multiple_results_use_latest | true | With multiple matches allowed, use the newest record (false uses the oldest) |
Bulk upsert
Upserts many rows in one all-or-nothing transaction. Rows without an id or _search_value are
created. The ids come back in input order.
1
2
3
4
5
6
const results = await pylo.invoice.bulkUpsert([
{ invoice_number: 'INV-002', amount_net: 500 },
{ id: 'invoice-uuid', amount_net: 750 },
]);
// results: { id: string }[]Placeholder ids
A new record you need in more than one place in the same request gets a placeholder as its id:
${replace_uuid.<key>}. The first mention creates the record and must carry the fields a create
needs. Every later mention with the same key in the same request refers to that record, here one
customer for both invoices. The same key in the next request is a new record.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mutation {
first: upsertInvoice(input: {
invoice_number: "INV-003"
customer_set: { id: "${replace_uuid.acme}", name: "Acme Corp" }
}) {
data { id }
replaced_uuids { name uuid }
}
second: upsertInvoice(input: {
invoice_number: "INV-004"
customer_set: { id: "${replace_uuid.acme}" }
}) {
data { id }
}
}replaced_uuids maps each key to the UUID it became. Placeholders work in every upsert, including
schema changes, where they connect several new entities in one call (see
Schema changes). Inside a string field, a placeholder is only replaced
when the field is listed in _replace_vars.
Delete
1
2
const result = await pylo.invoice.delete(['invoice-uuid-1', 'invoice-uuid-2']);
// result: { success: boolean }Client hooks
usePyloUpsert
Invalidates the entity's list queries on success.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use client';
import { pylo } from "@/lib/pylo-hooks";
const { mutateAsync } = pylo.usePyloUpsert('invoice');
await mutateAsync({
_search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1000,
});usePyloBulkUpsert works the same way with an array input.
usePyloDelete
1
2
3
const { mutateAsync } = pylo.usePyloDelete('invoice');
await mutateAsync(['invoice-uuid-1']);Query invalidation
Mutation hooks invalidate all queries for the same entity, so list and detail views refetch. Query key structure:
- Lists:
['pylo', entityName, 'list', options] - Infinite lists:
['pylo', entityName, 'infiniteList', options] - By ID:
['pylo', entityName, 'byId', id, options]
Mutations invalidate the prefix ['pylo', entityName], which covers all of the above.