{
  "id": "define-and-deploy-features",
  "title": "Define and deploy features",
  "url": "https://redis.io/docs/latest/develop/ai/featureform/define-and-deploy-features/",
  "summary": "Author a Python definitions file and apply it to a Redis Feature Form workspace.",
  "tags": [],
  "last_updated": "2026-07-30T11:11:54+01:00",
  "page_type": "content",
  "content_hash": "b51894e8759cea1e2bd66679fc26b513209f0b3c6858b8fdd55441e72c0b1cc9",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "A feature workflow starts with a Python [definitions file](https://redis.io/docs/latest/develop/ai/featureform/concepts#definitions-files-and-ff-apply). This file declares the [entities, datasets, transformations, features, labels, training sets, and feature views](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types) you want in a [workspace](https://redis.io/docs/latest/develop/ai/featureform/concepts#workspaces). Run `ff apply` to submit that file as the workspace's [desired state](https://redis.io/docs/latest/develop/ai/featureform/concepts#the-resource-graph). Feature Form compares the file with the workspace's current resource graph and applies only the differences. If Feature Form accepts the change, it commits a new graph version. The model is declarative — the file describes the end state, not the steps to get there. Re-applying the same file leaves the workspace unchanged."
    },
    {
      "id": "author-a-definitions-file",
      "title": "Author a definitions file",
      "role": "security",
      "text": "Redis Feature Form treats a Python definitions file as the source of a desired resource graph. The example below declares one complete workflow end to end: a Postgres dataset, a SQL rollup transformation, a feature with a 7-day aggregation, and a Redis-backed feature view.\n\n[code example]"
    },
    {
      "id": "typical-file-structure",
      "title": "Typical file structure",
      "role": "content",
      "text": "A definitions file typically declares resources in this order:\n\n1. **Import the module** with `import featureform as ff`, which exposes the resource builders and provider helpers.\n2. **[Entities](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types)** — identify the real-world objects features describe, such as `customer` or `order`. Other resources join on the entity's key column.\n3. **[Datasets](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types)** — point at an existing table, view, or file on an offline store. The data remains in its original location.\n4. **[Transformations](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types)** — produce new datasets from existing ones, expressed as SQL or as a Spark job.\n5. **[Features](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types) and [labels](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types)** — entity-keyed values served at inference time (features) and used as the prediction target offline (labels).\n6. **[Training sets](https://redis.io/docs/latest/develop/ai/featureform/concepts#resource-types) and [feature views](https://redis.io/docs/latest/develop/ai/featureform/concepts#feature-views-and-serving)** — join features with a label on the entity key (training set) and expose features for online serving (feature view).\n7. **Export a `resources = [...]` list** that names every resource above. See [Definitions files and `ff apply`](https://redis.io/docs/latest/develop/ai/featureform/concepts#definitions-files-and-ff-apply) for how the loader uses it.\n\n\nIf your file doesn't export `resources = [...]`, `ff apply` falls back to its auto-registration registry. Prefer the explicit list during onboarding; it's easier to reason about and is what the [Quickstart](https://redis.io/docs/latest/develop/ai/featureform/quickstart) uses."
    },
    {
      "id": "the-file-should-reference",
      "title": "The file should reference",
      "role": "content",
      "text": "- Registered [provider names](https://redis.io/docs/latest/develop/ai/featureform/register-providers) such as `demo_postgres` and `demo_redis`. Providers must already exist in the workspace before they're referenced.\n- [Secret references](https://redis.io/docs/latest/develop/ai/featureform/concepts#secrets-and-secret-references) such as `env:PG_PASSWORD`, resolved at runtime by a [secret provider](https://redis.io/docs/latest/develop/ai/featureform/register-providers#configure-secret-providers) registered in the workspace.\n- Stable resource names that make sense across re-apply cycles, since the graph compares the file to current state by name."
    },
    {
      "id": "the-file-should-not-do",
      "title": "The file should not do",
      "role": "content",
      "text": "- Don't replace provider registration.\n- Don't assume providers exist before the workspace registers them.\n- Don't mix infrastructure provisioning into the definitions entrypoint."
    },
    {
      "id": "apply-a-definitions-file",
      "title": "Apply a definitions file",
      "role": "content",
      "text": "`ff apply` reads the file you pass with `--file` and submits its resources to a workspace. `--file` accepts a Python file, a package `__init__.py`, or a package directory containing one.\n\nGet the workspace ID with `ff workspace list`. See [Manage workspaces](https://redis.io/docs/latest/develop/ai/featureform/manage-workspace) for the full workspace lifecycle."
    },
    {
      "id": "preview-with-plan",
      "title": "Preview with `--plan`",
      "role": "content",
      "text": "Preview the change without applying it:\n\n[code example]\n\nUse this before large changes or whenever the file might be incomplete relative to the workspace's full desired state."
    },
    {
      "id": "standard-apply",
      "title": "Standard apply",
      "role": "content",
      "text": "Apply the file and wait for it to finish:\n\n[code example]\n\nWithout `--wait`, `ff apply` returns as soon as the server accepts the request and runs the job asynchronously. `--wait` blocks until the job reaches a target state: `--wait-for finished` waits for terminal success; `--wait-for running` returns as soon as the job is actively running.\n\nIf you skip `--wait`, the response includes a job ID. Check the job's status and per-task progress with:\n\n[code example]"
    },
    {
      "id": "apply-modes",
      "title": "Apply modes",
      "role": "content",
      "text": "- **Default apply** replaces the workspace's current resource graph with the file.\n- **`--merge`** applies a partial definition file without treating omitted resources as deletions.\n- **`--update`** is an advanced scheduler-backed mode that re-runs supported resources' normal update or incremental path, even when the graph definition is unchanged.\n- **`--full-rematerialize`** is an advanced scheduler-backed mode that forces full-refresh behavior on supported materialized resources.\n\nUse only one of `--merge`, `--update`, or `--full-rematerialize` at a time. Support for `--update` and `--full-rematerialize` is resource-family dependent — run `--plan` first to inspect the planned job, and pair them with `--wait` to see the outcome."
    },
    {
      "id": "verify-the-apply",
      "title": "Verify the apply",
      "role": "content",
      "text": "After apply finishes, confirm the change with:\n\n[code example]\n\nThe graph commands show the resources Feature Form recognizes; `ff catalog` shows where each materialized resource physically landed. See [Query data](https://redis.io/docs/latest/develop/ai/featureform/query-data) for more inspection options."
    },
    {
      "id": "if-apply-fails",
      "title": "If apply fails",
      "role": "content",
      "text": "Common reasons:\n\n- **Provider not registered.** A resource references a provider name the workspace doesn't know. Confirm with `ff provider list --workspace <workspace-id>` and register the missing provider per [Register providers](https://redis.io/docs/latest/develop/ai/featureform/register-providers).\n- **Secret can't be resolved.** A provider config uses a reference such as `env:PG_PASSWORD`, but the Feature Form server's environment doesn't expose that variable. Check the secret provider with `ff secret-provider get env --workspace <workspace-id>`.\n- **No resources to apply.** The entrypoint produced no resources. Make sure your file exports a `resources = [...]` list, or that auto-registration finds the resources you declared.\n- **Validation error.** The CLI prints the specific resource and field that failed; fix the file and re-run with `--plan`.\n\nFor more detail, re-run with `--verbose` to enable debug logging to stderr. For most failures, though, the apply job itself surfaces clearer errors than the debug log — let `--wait` finish, or run `ff scheduler job get <job-id>`, before reaching for `--verbose`."
    }
  ],
  "examples": [
    {
      "id": "author-a-definitions-file-ex0",
      "language": "python",
      "code": "import featureform as ff\nfrom datetime import timedelta\n\npostgres = ff.get_postgres(\"demo_postgres\")      # Look up a provider already registered in the workspace\n\ncustomer = ff.Entity(name=\"customer\")            # Entity: the real-world object features describe\n\ntransactions = postgres.dataset(                 # Dataset: registers an existing table with the graph\n    name=\"transactions_raw\",\n    table=\"transactions\",\n    timestamp_column=\"timestamp\",\n)\n\n# Transformation: derives a new dataset from existing ones via SQL\n@postgres.sql_transformation(name=\"customer_daily_rollups\", inputs=[transactions])\ndef customer_daily_rollups() -> str:\n    return \"\"\"\n        SELECT customer_id,\n               date_trunc('day', timestamp) AS event_day,\n               SUM(transaction_amount) AS total_amount\n        FROM {{transactions_raw}}\n        GROUP BY 1, 2\n    \"\"\"\n\n# Feature: an aggregated value served to models at inference time\ncustomer_total_amount_7d = (\n    ff.Feature(name=\"customer_total_amount_7d\")\n    .from_dataset(customer_daily_rollups, entity=\"customer\",\n                  entity_column=\"customer_id\", value=\"total_amount\",\n                  timestamp=\"event_day\")\n    .aggregate(function=ff.AggregateFunction.SUM, window=timedelta(days=7))\n)\n\ncustomer_risk_view = ff.FeatureView(             # Feature view: groups features behind a serving contract\n    name=\"customer_risk_feature_view\",\n    entity=\"customer\",\n    features=[customer_total_amount_7d],\n    inference_store=\"demo_redis\",\n)\n\n# Export every resource so ff apply registers them as the desired graph\nresources = [\n    customer,\n    transactions,\n    customer_daily_rollups,\n    customer_total_amount_7d,\n    customer_risk_view,\n]",
      "section_id": "author-a-definitions-file"
    },
    {
      "id": "preview-with-plan-ex0",
      "language": "bash",
      "code": "ff apply \\\n  --workspace <workspace-id> \\\n  --file examples/featureform/docs/resources.py \\\n  --plan",
      "section_id": "preview-with-plan"
    },
    {
      "id": "standard-apply-ex0",
      "language": "bash",
      "code": "ff apply \\\n  --workspace <workspace-id> \\\n  --file examples/featureform/docs/resources.py \\\n  --wait \\\n  --wait-for finished",
      "section_id": "standard-apply"
    },
    {
      "id": "standard-apply-ex1",
      "language": "bash",
      "code": "ff scheduler job get <job-id>",
      "section_id": "standard-apply"
    },
    {
      "id": "verify-the-apply-ex0",
      "language": "bash",
      "code": "ff graph workspace stats --workspace <workspace-id>\nff graph feature list --workspace <workspace-id>\nff catalog list --workspace <workspace-id>",
      "section_id": "verify-the-apply"
    }
  ]
}
