Two patterns, five services, one n8n workflow A developer built a single n8n workflow that hosts five AI document-processing services—structured extraction, translation refinement, OCR, PDF conversion, and text extraction—using dynamic dropdowns, dynamic credentials, and a Switch node for routing. The workflow, part of the n8n-nodes-ldxhub package, demonstrates two distinct patterns: a single-page form for simple services and a multi-page form for services requiring sequential choices. The developer emphasizes that the patterns are generic and can be reused for consolidating any set of services into one template. The first two articles in this series each showed one technique. Implementation notes 001 https://dev.to/hidekimori/build-a-multi-step-n8n-form-with-dynamic-dropdowns-no-plugin-needed-1pm4 was a dynamic dropdown — a form field that fills itself from an API. Implementation notes 002 https://dev.to/hidekimori/let-your-n8n-template-ask-for-the-users-api-key-54n9 was a dynamic credential — an API key that arrives from the form and threads through to the HTTP nodes. This article is the capstone. It walks through all-services-demo , the example workflow that ships with n8n-nodes-ldxhub , where those two techniques combine with a Switch node to host five different AI document-processing services inside one workflow — structured extraction, translation refinement, OCR, PDF conversion, and text extraction. The screenshots and the workflow JSON below come from the n8n-nodes-ldxhub package. The patterns themselves are generic — they work for any set of services you want to consolidate into a single template. This is not a "follow these steps" article. It's a parts catalog. No two readers are solving the same problem, and templates rarely fit anyone's situation as-is. Take what fits. Drop the rest. You don't need to understand all 46 nodes to reuse the patterns. The workflow has 46 nodes — large enough to look intimidating in the editor, but structurally it's just five repeated paths plus a small routing section. The entry section is two nodes: Everything to the right of the Switch is service-specific. Five paths fan out: StructFlow, RefineLoop, RenderOCR, CastDoc, ExtractDoc. Each path ends in two Form Ending nodes — one for success auto-downloads the result , one for error. That's the spine: form → switch → service path → ending. The complexity is pushed into the service paths. The Switch node "Route by Service" uses Rules mode. Each rule reads the same expression from the form — {{ $json.service }} — and compares it to a static service name. Rule 1: {{ $json.service }} is equal to structflow → output: structflow Rule 2: {{ $json.service }} is equal to refineloop → output: refineloop Rule 3: {{ $json.service }} is equal to renderocr → output: renderocr Rule 4: {{ $json.service }} is equal to castdoc → output: castdoc Rule 5: {{ $json.service }} is equal to extractdoc → output: extractdoc The read side is dynamic the expression resolves to whatever the user picked . The match side is static fixed strings . That asymmetry is intentional. Static rules mean adding a new service is a manual edit — open the Switch node, add a row, save. No regeneration, no template hooks, no auto-discovery. Boring and unsurprising. This is the part you can lift cleanly: a Switch with N static rules driven by one expression from upstream. It works for service routing, document type routing, user tier routing, anything that fans into discrete branches. Once you start reading the service paths, you notice something: they are not all the same shape. There are two distinct patterns. Get Models HTTP → Derive Options Set → Run Form Form, next page → Inject Binary Code → Run LDX hub → Download / Error Form Endings One form page collects everything the user needs to choose. The model selection, the input, the parameters — all in one screen. There's only one form page after the Switch. Get Engines HTTP → Select Engine Form, next page → Derive Options Set → Upload File Form, next page → Filter by File Set → Select Output Form, next page → Inject Binary Code → Run LDX hub → Download / Error Form Endings Three form pages, each gated on the previous one. First the engine is chosen. Then the file is uploaded. Then the output format is selected — and the available outputs are filtered based on what the chosen engine supports for the uploaded file type. The Filter by File Set node sits in the middle of that dependency. The shape of the path follows the shape of the user's decisions. When the choices are independent — pick a model, pass some data — one form page is enough. When the choices cascade — engine restricts file types, file restricts output formats — the form has to be split, and intermediate Set nodes have to filter the options between pages. I tried to force a single pattern across all five services. It made the simpler services more complicated than they needed to be. The honest design was to let the cascading services be longer, accept the asymmetry, and document it. Two patterns isn't a sign of incompleteness. It's the consolidation accepting that two shapes were genuinely warranted. Walking through StructFlow gives you the vocabulary for all five paths. The other four are variations. Get Models — an HTTP node that hits /structflow/models and returns the available LLMs gpt-5.5, claude-sonnet-4-6, gemini-3-flash, etc. . This is the data source for the dropdown. Derive Options — a Set node that reshapes the model list into the format n8n's Form trigger wants for a dropdown: {name, value}, ... . Same trick as in 001 https://dev.to/hidekimori/build-a-multi-step-n8n-form-with-dynamic-dropdowns-no-plugin-needed-1pm4 — derive the dropdown from data, not from a hardcoded list. Run Form — a single form page that asks for everything: which model to use, the input data, any parameters. The "model" dropdown reads its options from the upstream Derive Options node. Inject Binary — a Code node that does one thing. In n8n, uploaded files travel through the workflow as binary data, separate from the JSON fields, and some intermediate nodes like Set only preserve the JSON side. By the time data reaches the LDX hub node, the binary part has been dropped. js return $input.all .map item = { json: item.json, binary: $ 'StructFlow: Run Form' .item.binary } ; The Code node reaches back to the form node and re-attaches the binary. One line of glue, but without it the file disappears. Run — the LDX hub custom node, with runJob: structFlow . This is where the API call actually happens. The credential is set in expression mode to read from the form input — the dynamic credential pattern from 002 https://dev.to/hidekimori/let-your-n8n-template-ask-for-the-users-api-key-54n9 . Download / Error — two Form Ending nodes. The Run node has two output ports: Success goes to Download which serves the result file , Error goes to Error which shows the error message . Five other paths follow the same idea. The names change, the number of form pages changes, but the role of each node is the same. All five service paths end at the same LDX hub custom node. Same node type, same credential, same shape — only the runJob parameter differs: StructFlow: Run → runJob: structFlow RefineLoop: Run → runJob: refineLoop RenderOCR: Run → runJob: renderOcr CastDoc: Run → runJob: castDoc ExtractDoc: Run → runJob: extractDoc This is the abstraction the custom node provides. From the workflow's perspective, "running a service" looks identical across the five paths. The differences are buried inside the node's implementation, where they belong. This generalizes the idea from 002's sidebar https://dev.to/hidekimori/let-your-n8n-template-ask-for-the-users-api-key-54n9 : a custom node that hides its variations behind a uniform interface lets you compose it freely. Five services. One credential type. One node. Five jobs. The workflow author doesn't have to know how StructFlow differs from RenderOCR — the node knows. If you're building your own custom node, this is the shape worth aiming for. One node, parameterized by job kind. The workflow stays clean. The variations stay encapsulated. Every service path has two endpoints: Download success and Error. Both are Form Ending nodes. Both are visible to the user. This isn't decorative. A distributable template has one minimum obligation: once the user clicks Submit, they need to see what happened . If the call succeeded, they get the result. If it failed, they get the error. There's no third state where the form just ends silently. Earlier failures — the Get Models call returning empty, the Inject Binary node crashing — are not handled. Those are skipped here because the template is meant to be minimal, and because adding error endings everywhere makes the canvas unreadable. But the final Run node's error branch is mandatory. That's the one place where the user's expectation "I started a job, what happened?" has to be answered. Where there's an if , you need an else . The else doesn't have to be elegant. It just has to exist. This is the part you can lift on its own: any time a workflow has a user-visible "run" step, pair its success with an error ending. Other branches can be skipped or logged, but the user-facing one is non-negotiable. The technique parts above — Switch routing, two form patterns, Binary injection, Run convergence, if/else error pairing — are each portable. You can lift them one at a time. But the article would be missing something if it stopped there. Bringing five services into one workflow is itself the work. Not a tutorial-friendly kind of work, because nothing in this section is a discrete technique. It's a series of small decisions: