For the complete documentation index, see llms.txt. This page is also available as Markdown.

File Version Produce Plugin

How to setup fylr to call an external HTTP service provided by a plugin to generate file renditions

How to make fylr call an external HTTP service for a rendition.

Assumed to exist already:

  • a conversion service running under a known URL, for example http://converter:8000/convert

  • a produce plugin, meaning a plugin that ships a cookbook whose recipe calls that URL and that takes the URL as a recipe parameter

Two names are used throughout: the produce plugin is the piece installed in fylr, and the conversion service is the HTTP endpoint it calls. They are separate things and they usually run in separate places.

fylr's official example plugin, fylr-plugin-example, carries working produce recipes and is the reference to compare against whenever something below does not match what you see. The Further reading section says which files to look at.

Everything else is set up in the base configuration under File worker > Preview configuration

What lives where

The split matters, because it decides what you can change in the frontend and what needs a plugin release:

Thing
Defined in
Changeable in the frontend

the cookbook and its recipes

plugin (or fylr's built-ins)

no

what the command line looks like

plugin

no

which recipe parameters exist

plugin

no

which classes and extensions exist

base config

yes

which versions are produced

base config

yes

the parameter values, including the endpoint URL

base config

yes

So the conversion service's address is base config data. Moving the service to another host or port is a base config edit, not a plugin change and not a restart.

1. Install and enable the plugin

Install the plugin in the plugin manager, then make sure it is enabled. With fylr.plugin.default.enabled: false a freshly installed plugin comes up disabled, and a disabled plugin contributes no cookbooks.

The recipe is now addressable as <plugin>:<cookbook>:<recipe>. fylr's built-in recipes have no plugin part and are just <cookbook>:<recipe>. You need that exact string in step 3.

Do this before step 3: the base config validates recipe names on save, and an unknown recipe is a hard save error.

Check whether the plugin already brings its own configuration. A plugin may ship a produce config of its own, declared as fas_config.produce_config in its manifest, and fylr merges that into the compiled configuration for every enabled plugin. If it does, the class and the version exist the moment the plugin is enabled and there is nothing to do in the renditions editor. fylr-plugin-example works this way through fas_config/custom_produce.yml.

The merge is additive: plugin versions are appended to what the base config defines, they do not replace it. Two consequences are worth knowing. Versions contributed by a plugin do not appear as editable entries in the renditions editor, so their parameters, including the endpoint URL, can only be changed in the plugin. And a plugin claiming an extension that another class already claims makes the whole configuration fail to compile, because extensions must be unique across all classes.

2. Check reachability from the execserver, not from your machine

The recipe's command runs in the execserver, not in the fylr server and certainly not in your browser. The endpoint you type into the base config is resolved there.

  • execserver as its own container: 127.0.0.1 is the execserver's loopback. Use the service name on the shared docker network, and put the conversion service on that network.

  • execserver on the same host as everything else: 127.0.0.1:<port> is fine.

A URL that answers from your terminal proves nothing about the execserver. If in doubt, get a shell in the execserver container and curl it from there.

3. Open the renditions editor

Base configuration > File worker > Preview configuration

That editor is a view onto one JSON document, the same document you would POST to /api/v1/config as system.config.produce_config.value. Its top level is a map of classes, and each class holds upload rules and a list of versions.

Two ways in:

  • extend an existing class if the file type is already accepted on upload (a JPEG, a PDF) and you only want one more rendition produced for it. Add a version to that class and stop.

  • add a new class if the file type is in no class yet. An extension that belongs to no class is refused at upload time with Filetype "xyz" is unknown and not allowed, so the class has to come first.

4. Class settings (new class only)

Field
Meaning

class name

free, lowercase; it is the key of the class, and it is what the recipe's class must match

uploadenabledextensions

every extension the class accepts on upload. Must be unique across all classes: no extension may appear in two classes

uploadmaxfilesize

for example 2gb

5. Add the version

A version is one rendition produced from the upload. Fields worth setting:

Field
Meaning

name

unique per class, no leading or trailing spaces, not empty

extensions

which uploads this version is produced for. Must all appear in the class's uploadenabledextensions, and must all be supported by the recipe. Empty means every extension, which is usually not what you want

recipename

the <plugin>:<cookbook>:<recipe> string from step 1

params

the recipe's parameters. This is where the conversion service URL goes

group

preview, thumbnail or huge: how the frontend groups it in download and URL selections

standard

include it in _standard.eas lists, meaning it becomes part of the normal payload delivered with the asset

displayname

per-language label

sourceversion

produce from another version instead of from the original. Leave empty for the original

rightsmanagement, watermark

flags the server and frontend act on

Parameter values are checked against the recipe's declared type, ranges and select options at save time. A typo in a number range or an option is a save error, not a runtime surprise.

If different extensions of one class need different recipes, add several version entries with the same name, each with its own extensions and recipe. That is supported, but standard, rightsmanagement, watermark and group must be identical on all of them.

6. Save, and read the error if it fails

The save runs the full produce config check. The messages are precise; these are the ones you will actually hit:

Message
Cause

uses an unknown recipe "…"

plugin not installed, not enabled, or the recipe string is misspelled

misses upload enable for extension "…"

the extension is on the version but not in the class's uploadenabledextensions

Extension "…" is also defined in "…"

two classes claim the same extension

doesn't support the class "…"

the recipe's class is not the class you put it under

doesn't support the extension "…"

the extension is not in the recipe's extension list

contains an invalid version "…"

empty version name or surrounding whitespace

contains contradicting settings for version "…"

same version name twice with differing standard, rightsmanagement, watermark or group

Mandatory parameter '…' missing, Value '…' is higher than maximum …, Unsupported param value '…'

parameter values rejected against the recipe's declaration

unknown source version "…"

sourceversion names a version that does not exist in this class

Note the asymmetry: on save an unsupported extension is rejected, while on load (server start, plugin gone) the same situation is handled leniently and the extension, possibly the whole version, is silently dropped with a warning in the log. So a version that quietly stops being produced after a plugin was disabled is expected behaviour, and the log is where it says so.

7. Verify

Upload a matching file. The new version should appear on the asset.

If it does not, look for a FILE_PRODUCE_ERROR event. It carries the job receipt: the full command line, stdout and stderr. For a conversion service the usual causes are, in order of likelihood:

  1. the endpoint is not reachable from the execserver (see step 2)

  2. the service answered 4xx or 5xx and the recipe did not turn that into a non-zero exit, so the error page gets stored as the rendition and the version exists but is garbage

  3. the service returned a different format than the version's file extension claims

8. Later changes

  • Conversion service moved: edit the endpoint parameter on the version and save. No plugin change, no restart.

  • Endpoint per version: each version carries its own parameters, so two versions of the same class can point at two different services.

  • Existing assets are not re-produced by a base config change. Renditions are produced on upload, so changing the config affects new uploads and existing files need an explicit re-produce.

  • Removing the plugin while the config still references its recipe stops the version from being produced, dropped on load with a warning. Clean the version out of the base config rather than leaving a dangling reference.

Scope, once more

A recipe produces a version (rendition) attached to an uploaded file. It never creates a new asset. If the conversion service is supposed to turn an upload into a separate asset of its own, this is the wrong hook entirely: that is a server-side plugin callback uploading through /api/v1/eas.

If the goal is not a rendition at all but metadata extracted from the file, the hook is a metadata recipe (class _metadata) rather than a version. fylr-plugin-example has several of those.

Further reading

fylr-plugin-example is the official example plugin and covers the plugin side of everything above:

File
What it shows

manifest.yml, section fas_config

how a plugin registers its cookbooks and, optionally, its own produce config

fas_config/cookbook_grayscale.yml

two produce recipes in one cookbook: one calling a binary that exists in the execserver image, one running a script shipped with the plugin through %_exec.pluginDir%. A recipe calling a conversion service has the same shape and differs only in the command

fas_config/custom_produce.yml

a plugin shipping its own produce config, so the class and version exist as soon as the plugin is enabled

fas_config/cookbook_metadata_example.yml, fas_config/cookbook_metadata_pdf2text.yml

metadata recipes, including request-only ones that run lazily when a mapping asks for them

The recipes there run their converter inside the execserver instead of calling out to an HTTP service. That is the other way to build a produce plugin, and it avoids the reachability question in step 2 entirely, at the price of the converter having to exist in the execserver image or travel with the plugin.

Last updated