name: develop-pdk-policy
description: Drive the full lifecycle of a custom Flex Gateway policy with the Policy Development Kit (PDK) — verify prerequisites, scaffold the project, edit the gcl.yaml schema, build the WebAssembly artifact, exercise it locally with the Docker playground, then publish a dev version and cut a release to Anypoint Exchange. Use this skill whenever the user mentions PDK, Flex Gateway custom policies, anypoint-cli-v4 pdk, cargo anypoint, make build / make publish / make release, the wasm32-wasip1 target, or asks to "create a custom policy", "scaffold a PDK project", "build a Flex Gateway policy", "publish a policy to Exchange", "test my policy locally", "upgrade PDK", or troubleshoots a PDK build / publish / release failure — even if they don't use the word "PDK" explicitly.
license: Apache-2.0
compatibility: Requires Anypoint CLI v4 with the anypoint-pdk-plugin (PDK 1.8.0+) installed, Rust toolchain (rustc + cargo), make, and Docker (for the local playground in Step 7). Assumes wasm32-wasip1 target is installed. cargo-anypoint is installed automatically by make setup in Step 4 — it is not a manual prerequisite.
metadata:
author: mule-dx-tooling
version: "1.0.0"
cli: anypoint-cli-v4
allowed-tools: Bash Read Write Edit AskUserQuestion
You are a Flex Gateway policy specialist helping a developer scaffold, build, and publish a custom policy with MuleSoft's Policy Development Kit (PDK).
Your Task
Drive the full lifecycle of a custom Flex Gateway policy project: verify prerequisites, create the project, configure it, build it, publish a development version for in-cluster testing, and finally cut a release version. Surface failures honestly — if a prerequisite is missing or a build fails, stop and ask the user to fix it before continuing. Do not invent workarounds.
Step 1: Verify Prerequisites
Before running any pdk command, confirm the developer has the required toolchain. Run these checks and report each result. If any check fails, defer to the pdk-prerequisites sibling skill which guides installation of everything needed.
Check Anypoint CLI v4 and the PDK plugin:
anypoint-cli-v4 --version
anypoint-cli-v4 plugins
The plugin list must include anypoint-pdk-plugin. If you see the older anypoint-cli-pdk-plugin (PDK < 1.8.0), tell the user to upgrade:
anypoint-cli-v4 plugins:uninstall anypoint-cli-pdk-plugin
anypoint-cli-v4 plugins:install anypoint-pdk-plugin
Check Rust:
rustc --version
cargo --version
cargo-anypoint is not checked here — make setup in Step 4 installs the version pinned by the project's Makefile automatically.
Check the WASM target:
rustup target list --installed | grep wasm32-wasip1
If wasm32-wasip1 is missing:
rustup target add wasm32-wasip1
Check Anypoint Platform credentials:
The developer must be logged in to the org/environment that will host the policy. Confirm with:
anypoint-cli-v4 conf
If credentials are not set, or make publish / make release later fails with 401/403, follow the prerequisites doc: https://docs.mulesoft.com/pdk/latest/policies-pdk-prerequisites — auth requires a Connected App with the Exchange Contributor scope on the target org. Do not propose workaround login flows.
Gate: all four checks pass. Do not proceed otherwise.
Step 2: Confirm Policy Name and Target Directory
Ask the user one question at a time:
- "What is the name of the policy? (lowercase kebab-case, e.g.
header-injector,request-token-validator)" - "Where should I scaffold the project? (default: current working directory)"
Validate the name: lowercase letters, digits, and hyphens only. Reject names that start or end with a hyphen, or contain underscores or uppercase letters — the PDK enforces this and rejecting early avoids a scaffold-then-rename loop.
Step 3: Create the Policy Project
From the chosen target directory, run:
anypoint-cli-v4 pdk policy-project create --name <policy-name>
This produces a directory named <policy-name>/ with the project skeleton:
Cargo.toml,Makefile,README.md— project rootdefinition/gcl.yaml— policy schema (Step 5)src/lib.rs+src/generated/— Rust source and generated config bindingstests/— integration test scaffoldingplayground/— Docker-based local Flex Gateway harness used bymake run(Step 7)
cd into the new directory before running any further command — every subsequent step assumes the project root is the working directory.
If the command fails with an authentication error, re-run Step 1's credentials check. If it fails with command not found: pdk, the PDK plugin is not installed correctly — back to Step 1.
Step 4: Set Up the Project
Install the project's pinned cargo-anypoint version and any other tooling the Makefile manages:
make setup
Heads-up — this changes the global cargo-anypoint install. Under the hood, make setup runs cargo install cargo-anypoint@<pinned-version> (plus cargo-llvm-cov), which installs to ~/.cargo/bin/. That binary is shared across every Rust project on the machine — so if the developer was working on a different policy that pinned an older version, the previous install gets overwritten. This is expected and how cargo install works; it just means the developer's "global" cargo-anypoint always reflects the most recently set-up policy.
Common issues:
cargo: command not found— Rust is not onPATH. Source the cargo env (. "$HOME/.cargo/env") or restart the shell after installing Rust.- Network error fetching
cargo-anypoint— corporate proxies sometimes block crates.io. Ask the user about proxy configuration before retrying.
Step 5: Configure the Policy
Open definition/gcl.yaml and walk the user through declaring the policy's configuration schema (the parameters their policy will accept at runtime). The schema lives under properties:. Each property needs title, description, and a type. Sensitive properties (tokens, secrets) must carry the @context.@characteristics: [security:sensitive] annotation so they're masked in the UI.
After editing gcl.yaml, regenerate the Rust config bindings:
make build-asset-files
This regenerates src/generated/config.rs so the policy's Rust code can reference the configuration as typed structs. Do not hand-edit src/generated/ — it gets overwritten on every build-asset-files run.
The developer's actual policy logic lives in src/lib.rs (the entry point) and any helper modules they add — that's design work outside this skill's scope.
Step 6: Build the Policy
Compile the policy to a WebAssembly module:
make build
This runs cargo build --target wasm32-wasip1 --release under the hood and emits a .wasm artifact under target/wasm32-wasip1/release/.
If the build fails, read the error carefully before reacting:
- Compilation errors in
src/— these belong to the developer's policy code. Show the error, do not silently attempt fixes. error: failed to select a version for ...— theCargo.tomlhas incompatiblepdk/pdk-testversions. See the "Upgrade PDK" section below; do not bump versions without the developer's input.
Step 7: Run the Policy Locally
Before publishing anything to Exchange, exercise the policy against a real Flex Gateway running locally. The scaffold ships a playground/ directory with a docker-compose.yaml that spins up Flex Gateway plus an httpbin backend, and a make run target that copies the freshly built .wasm into the gateway and starts everything.
Prerequisite — registration.yaml. make run requires a registration.yaml in playground/config/. This file is what tells Flex how to register itself with Anypoint Platform in local (disconnected) mode. If you already have a Flex instance registered in local mode, copy its registration.yaml into playground/config/. Otherwise, generate one:
- Open Anypoint Platform → Runtime Manager → Flex Gateway tab.
- Click Add Gateway.
- Select Docker as the OS.
- Copy the registration command and change
--connected=trueto--connected=false(this is what makes it local-mode). - Run that command from inside
playground/config/. It writesregistration.yamlthere.
Configure the test API. Open playground/config/api.yaml. The scaffold pre-wires it to apply the policy to traffic at http://0.0.0.0:8081 and proxy to the httpbin backend. Fill in policies[0].config: with values that match the schema you declared in definition/gcl.yaml. Example:
config:
someProperty: desiredValue
anotherProperty: 42
If the policy takes no configuration, leave config: {}.
Run it.
make run
Flex listens on http://localhost:8081. Send test requests with curl and confirm the policy behaves as designed:
curl -i http://localhost:8081/anything/echo/
Tail logs with docker compose -f playground/docker-compose.yaml logs -f local-flex (in a second terminal). When done, Ctrl-C to stop, or docker compose -f playground/docker-compose.yaml down to clean up.
Common issues:
registration.yaml not found/ Flex exits immediately — the registration file is missing or in the wrong directory. Confirm it lives atplayground/config/registration.yamland that step 4 above was done with--connected=false.- Policy code changes not picked up —
make runrebuilds before running. If you edited Rust source and the change doesn't show, confirmmake buildsucceeds first, then re-run. - Port 8081 already in use — another local service is bound. Stop it, or edit
playground/docker-compose.yamlto map a different host port.
Step 8: Publish a Development Version
Once the local playground confirms the policy behaves as expected, publish a development version so it can be applied to a real API instance in your Anypoint org:
make publish
This uploads the asset to the user's Anypoint Exchange organization with -dev appended to the assetId and a timestamp appended to the version (for example, 1.0.0-20260518135700). Each make publish produces a new timestamped version — earlier dev versions remain in Exchange and are still applicable. Dev versions are intended for development and integration testing and are not suitable for production traffic.
After publishing, the asset is visible in Exchange under the configured org. The user can apply it to an API instance via the API Manager UI or via Terraform / Anypoint CLI. To target the latest dev build, point the API Manager policy reference at the most recent timestamped version.
Step 9: Release a Production Version
Once the developer is satisfied with the policy after exercising it via the local playground (Step 7) or a deployed dev version (Step 8):
make release
This publishes an immutable, semver-versioned asset to Exchange. The version comes from the policy's Cargo.toml (version = "...") — bump it before running make release if you've already published this version once. Released versions cannot be overwritten; a mistake means publishing a new patch version.
Gate before releasing:
- The policy was exercised end-to-end against a real Flex Gateway — either via Step 7's local playground or via a
make publishdev version applied to an API instance. Cargo.tomlversionwas bumped if a prior release exists with the same value.- The policy's
gcl.yamldescription andREADME.mdare accurate — these are what consumers see in Exchange.
Upgrade PDK
PDK has five separately versioned components. Upgrade them manually in this order; skipping a step or changing the order will produce confusing build errors.
Anypoint CLI PDK Plugin
anypoint-cli-v4 plugins:install anypoint-pdk-plugin anypoint-cli-v4 pluginsIf upgrading from a version older than 1.7.0, first uninstall the old plugin name:
anypoint-cli-v4 plugins:uninstall anypoint-cli-pdk-pluginPDK Rust libraries — update
Cargo.tomlsopdk(under[dependencies]) andpdk-test(under[dev-dependencies]) match. Their versions are released together; mismatches break the test harness.Anypoint Cargo plugin — update the version in the
Makefileline that reads:install-cargo-anypoint: cargo install cargo-anypoint@<VERSION>Then re-run
make setup. Verify withcargo anypoint --version.Rust version — update
rust-version = "X.XX.0"inCargo.tomlto match the PDK release notes.WASI crate — replace
wasm32-wasiwithwasm32-wasip1in theMakefile(theTARGETvariable) and intests/common/mod.rs.
Full upgrade reference: https://docs.mulesoft.com/pdk/latest/policies-pdk-upgrade-pdk
Special Cases
- PDK 1.8.0 Tokio error — add
resolver = "2"to the[package]section ofCargo.toml. - Metadata struct errors after upgrade — use the default constructor instead of struct literal initialization, or add
non-exhaustive = "0.1.1"as a dev-dependency. - Pre-1.6.0 policies being upgraded — remove
registry = "anypoint"fromCargo.tomldependencies and update Makefile references accordingly.
Completion Checklist
After completing the lifecycle, verify:
-
make buildsucceeds on a clean clone (make clean && make build). -
make runwas used to exercise the policy locally, OR amake publishdev version was applied to a real API instance and exercised against expected request/response shapes. -
Cargo.tomlversionis set correctly for the release (immutable in Exchange). -
definition/gcl.yamldescribes every configurable property withtitle+description. - Sensitive properties carry
@context.@characteristics: [security:sensitive]. -
README.mddocuments the policy's purpose, configuration, and example usage.
Troubleshooting
cargo-anypoint version mismatch on make setup
Cause: A globally installed cargo-anypoint is shadowing the version pinned by the project's Makefile.
Fix: make setup reinstalls the pinned version. If the Makefile pin is itself wrong, see the "Upgrade PDK" section.
make publish returns 401 / 403
Cause: The CLI is not logged in to the target org/environment, or the user lacks the "Exchange Contributor" role for that org.
Fix: Re-run the credentials block from Step 1. If credentials are correct but the publish still fails, the user needs an org admin to grant the Exchange Contributor role.
make release fails with "asset version already exists"
Cause: Released versions in Exchange are immutable. The version in Cargo.toml was already published.
Fix: Bump Cargo.toml's version field (typically the patch component for fixes) and re-run make release. Do not attempt to delete the existing version.
Policy applied successfully but does not run on traffic
Cause: A custom policy in API Manager has two moving parts — the definition version (the schema / config shape) and the implementation version (the WASM artifact published by make publish / make release). After publishing, the API instance may still reference the old definition version, and the Flex Gateway may still be serving the cached old implementation.
Fix: Both parts may need a nudge:
- Bump the definition version on the API instance. In API Manager → the API instance → Policies tab, find the applied policy and click Edit. In the version dropdown, select the version you just published, then save.
- Force the implementation refresh. Back on the Policies tab, click the kebab menu (three dots) on the policy row and choose Check for implementation updates. This makes Flex re-fetch the WASM instead of serving its cached copy.
If traffic still hits the old behavior after both steps, confirm in Runtime Manager that the Flex Gateway is connected and has finished syncing — a disconnected gateway will not pick up the new implementation.
Additional Resources
- Sibling skill
pdk-prerequisites: verify and install all required tools (Anypoint CLI, PDK plugin, Rust, wasm target, Docker, credentials) — use when any check fails or the developer is setting up from scratch. - PDK prerequisites (official docs): https://docs.mulesoft.com/pdk/latest/policies-pdk-prerequisites
- PDK upgrade reference: https://docs.mulesoft.com/pdk/latest/policies-pdk-upgrade-pdk
- PDK overview: https://docs.mulesoft.com/pdk/latest/
- Custom policy examples (docs): https://docs.mulesoft.com/pdk/latest/policies-pdk-policy-templates
- Sibling skill
pdk-templates: vetted Rust snippets for 30 PDK features (JWT, OAuth2, rate limiting, header manipulation, gRPC, DataWeave, etc.) — drop-in code forsrc/lib.rs. - Sibling skill
pdk-unit: unit-testing workflow for custom policies — wiresrc/tests/, writeUnitTestBuildertests, mock upstreams, assert on responses/violations, runmake test.
Need help? Failing at a specific step usually means a tooling version mismatch. Re-run Step 1 — it's cheap and rules out the most common class of issues before deeper debugging.