Command Palette

Search for a command to run...

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.

example.ts
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:

example.ts
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.

OptionDefaultDescription
fieldrequiredField to search by
valueValue to match
not_found_behavior'create''create' inserts a new record, 'error' fails, 'ignore' skips (nested relation upserts only)
search_in_all_field_variantsfalseSearch across all variants of the field instead of only the requested ones
multiple_results_allowedfalseAllow more than one match; without it, multiple matches fail the mutation
multiple_results_use_latesttrueWith 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.

example.ts
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.

placeholder-ids.graphql
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

example.ts
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.

example.ts
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

example.ts
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.