Skip to main content

Custom Fields

Custom fields let you record your own facts on the things Printago already tracks: "hardened nozzle" on a printer, "customer" on an order, "shelf location" on a part, "batch" on a job. Printago will never model every fact a print farm cares about, so custom fields give you somewhere to put the ones that are specific to how you run yours.

There is no setup step. Open any printer, part, SKU, order, material, or job, find the Custom Fields section, and add a field.

Adding a field

On a detail page, scroll to Custom Fields. If the field is one you have already defined, it is on the Not set bar at the bottom of the section: click it and fill it in. For a new one, click Add field, type a name and a value, then press Enter or click Add. Cancel closes the row again and keeps nothing you typed.

The value is saved when you save the entity. On a printer or a part, that is the page's own Save button, the same one that saves the name and the tags. On a job, the Custom Fields section has its own Save custom fields button, because the rest of the job page is read-only.

To remove a field from an entity, click the × next to it and save.

Giving a field a type

A field with no type accepts anything. That is usually fine, and it is the fastest way to start tagging.

Giving a field a type does two things:

  • It is checked every time someone saves. A field typed as a number rejects "about 400". A field typed as a list of allowed values rejects anything outside that list. This catches data-entry mistakes at the moment they are made instead of weeks later.
  • It becomes available to routing rules. Only typed fields can be used to decide which printer a job goes to.

You can give a field a type from three places: Define field at the top of the Custom Fields section on a detail page, the small T button beside an untyped field on that same page, or New field in Settings > Account > Custom Fields.

The available types are:

TypeWhat it acceptsHow it looks on the page
TextAnythingA text box
NumberNumbers only, with an optional unit like "h" or "mm"A number box with the unit beside it
Yes / noTrue or falseA switch
Pick from a listOnly the values you listA dropdown
Date / timeA date, with or without a time of dayA date picker

The field's key is how the API and routing rules address it, so it cannot be changed once the field has a type. The label — what you actually read on the page — can be changed whenever you like.

Which fields appear on a record

A record lists the custom fields it actually has a value for, and nothing else. Defining a field does not put a row on every record of that kind: until a record holds a value for the field, that record simply does not have it, and the page says so by not showing it.

This keeps the page and the API telling the same story. If you can see a field on a record, the record has it: GET returns it and a routing rule reading it finds it. If you cannot see it, there is nothing there to find.

At the bottom of the Custom Fields section is a Not set bar listing the defined fields this record has no value for. Click one and it appears as a row, with its proper control, ready to fill in. Nothing is written until you give it a value, and the × on a row removes the field from the record and puts it back on the bar.

A field that declares a default value is the exception: adding it starts the row at that default rather than empty, whatever type it is, because the default is what the field starts at and this is the field starting. Change it or clear it like any other value.

Yes / no fields always carry a value once added, with or without a default, because a switch has nowhere to show "no value": adding one sets it to No straight away. Taking it back off the record is what makes it unset again.

To put a field on every record of a kind, give it a default value below. That writes a real value when the record is created, which is a different thing from drawing an empty row over a record that has nothing.

Default values

A field can start new records off with a value. Set Default value when you define or edit the field, and every record created from then on carries that value from the moment it exists: it is in the API, it is visible to routing rules, and it is a row on the page like any other value.

Date and time fields work slightly differently. A fixed date is almost never a useful default, so instead of a date box they offer Default to the creation time, which stamps each new record with the moment it was created. That is the one default the Not set bar does not fill in: the record it would be added to was created long ago, so the row comes up empty for you to set rather than claiming today.

Two things worth knowing:

  • Defaults only apply to new records. Setting one never reaches back and changes records that already exist. Those keep whatever they have, including nothing. Adding the field yourself from the Not set bar starts it at the default, but that is you putting it there, and nothing is stored until you save.
  • A default is a starting point, not a rule. Anyone can change or clear the value on a record afterwards, and clearing it leaves the record without the field, the same as any other custom field.

Names that collide

A name that differs from a defined field only by capitalization or by underscores and hyphens is not allowed — Hardened_Nozzle and hardenedNozzle are the same field as far as anyone reading the page is concerned, so Printago blocks the second one rather than letting the two drift apart.

Spelling still matters beyond that, because the failure a typo causes is invisible. A misspelled field saves perfectly happily, but a routing rule checking the correctly spelled one will quietly skip those printers, and nothing will look broken. The Not set bar at the bottom of the section lists every field you have already defined, so reach for a field from there rather than retyping its name.

The Custom Fields settings page

Open Settings > Account > Custom Fields. It is one table of your typed fields: the field, what it belongs to, its type, what new records start with, and how many records currently have a value for it. Filter by entity with the chips, click Edit on a row to change a field's label, its unit, its allowed values, its type, or its default, and click New field to define one from scratch.

Only typed fields appear here. A field that was added on a record without a type shows up only on the pages of the records that carry it.

Giving a type to a field that already has values, or changing an existing field's type, checks your existing data first, across every record that has the field, not a sample.

Values that fit the new type exactly are left alone. Values that can be read as the new type without losing anything (the text 412 becoming the number 412, the text TRUE becoming a yes) are rewritten to match, so the record stays saveable afterwards. Values that cannot be represented at all stop the change: Printago tells you how many there are and shows examples, and you choose whether to clear those specific values or pick a different type. Clearing removes the field only from the records it named, never from the records whose values were fine, and nothing is dropped without you confirming it. One deliberate case: a number under a field you are typing as Date / time counts as incompatible, because Printago will not guess whether a bare number was meant as a timestamp.

Removing a type does not delete any data. The field simply goes back to accepting anything, stays visible and editable on every record that has it, and stops being available to routing rules. Giving it a type again restores everything.

Where custom fields work

Printers, parts, SKUs, orders, order items, customers, materials, material variants, spools, jobs, builds, profiles, folders, and maintenance items all carry custom fields, and all of them can be read and written through the API.

Printers, parts, SKUs, orders, materials, and jobs also have an editor on their detail page. The rest are available to integrations and scripts today; their pages will grow an editor as people ask for one.

Setting custom fields from the API

There are two ways to write custom fields, and they follow different rules about what happens to the fields you did not mention. Read the next two sections together before you write your first integration.

Values are strings, numbers, or booleans. A Date / time field takes an ISO 8601 string: a date like 2026-09-02, or a date and time like 2026-09-02T14:30, with an optional Z or +02:00 zone. A bare number is rejected for a Date / time field, since there is no safe way to guess whether it was meant as a timestamp.

On the record's own call

Custom fields ride the record's own create and update calls as a customMetadata object.

Fields that declare a default are applied when the record is created, so a POST that leaves customMetadata out entirely comes back with those keys already set. A default never overwrites a key you sent yourself, even when the value you sent is false or 0, and it never reaches records that already exist.

On these calls customMetadata is one field, and sending it replaces the whole set — it is not merged key by key. This is the same rule every other field on these endpoints follows: sending tags replaces the tags, and sending customMetadata replaces the custom fields.

PATCH /v1/parts/{id} {"customMetadata": {"alpha": "1", "beta": "2"}} → alpha and beta are set
PATCH /v1/parts/{id} {"customMetadata": {"gamma": "3"}} → only gamma remains
PATCH /v1/parts/{id} {"name": "Bracket"} → custom fields untouched

So to change one field without disturbing the others, read the record, change the one key, and send the whole object back:

const part = await GET(`/v1/parts/${id}`);
await PATCH(`/v1/parts/${id}`, {
customMetadata: { ...part.customMetadata, shelf: "B-14" },
});

Leaving customMetadata out of the request entirely changes nothing — that is different from sending {}, which clears every custom field on the record. Removing a single field means sending the object without that key, which is exactly what the × button on the detail page does. Sending a key with a null value removes that field too; null is never stored.

On the custom fields endpoint

/v1/custom-metadata writes custom fields and nothing else. It takes the kind of record in the path, so the same call works for anything that carries custom fields, including folders, customers, builds, spools, profiles, order items, material variants, and maintenance items, which have no editor on a page.

PATCH merges and PUT replaces. This is the difference from the record's own call, where a customMetadata object always replaces.

PATCH /v1/custom-metadata/part/{id} {"customMetadata": {"shelf": "B-14"}}
→ shelf is set, every other custom field on the part is left alone

PUT /v1/custom-metadata/part/{id} {"customMetadata": {"shelf": "B-14"}}
→ shelf is the only custom field the part has left

A key sent as null is removed, on either verb. PUT with {} clears every custom field on the record.

The kind of record goes in the path as one of printer, part, sku, order, orderItem, customer, material, materialVariant, materialInstance, printJob, partBuild, profile, maintenanceItem, or folder.

Both verbs also take a batch. Leave the id off the path and put the records in the body as items, each with its own customMetadata, up to 200 per call. That covers a table of edits where every row gets a different value, and it covers stamping one value across a selection just as well — repeat the same fields on every item:

PATCH /v1/custom-metadata/part
{
"items": [
{ "entityId": "...", "customMetadata": { "shelf": "B-14" } },
{ "entityId": "...", "customMetadata": { "shelf": "C-02", "owner": null } }
]
}
→ each part's own fields are merged; the second also loses its owner

PUT /v1/custom-metadata/part
{ "items": [ { "entityId": "...", "customMetadata": {} }, ... ] }
→ each named part is left with exactly the document given for it

A batch is all-or-nothing: it runs in one transaction, every named record has to exist, and an id may appear only once. If any check fails the whole call is rejected and nothing is written. The response lists every record in the order sent, each with its fields as they stand after the write.

PATCH is what you want most of the time: it is the one write that does not need you to read the record first. The record is locked for the merge, so two integrations touching different fields on the same record cannot lose each other's changes.

If several integrations write the same field names to the same records, or use the replacing calls, give each one its own field names and have each read before it writes. Two writers that overwrite the whole object from stale reads will lose each other's changes, the same way two writers of any other field would.

Permissions

Anyone who can edit a record can set custom field values on it — editing a printer's custom fields needs the same permission as editing its name. The /v1/custom-metadata endpoint asks for exactly the same permission the record's own edit call does, so it is not a way around anything — including the addons: custom fields on a maintenance item need the Maintenance Tracker addon, and on an order, order item, or customer a commercial plan.

Defining, retyping, and deleting field types needs the Routing permission (routing.manage), which Admins and Store Owners have by default. Everyone can see which fields are typed, since the editor needs that to show the right input.

Limits

A single record can hold up to 64 custom fields. Text values are capped at 1024 characters and the whole set of fields on one record is capped at 16 KB. Field names can use letters, numbers, underscores, and hyphens — no spaces or dots.