Skip to content

Custom fields

LeadHunter ships with the obvious fields — name, website, phone, address, status — but every business tracks attributes specific to its market. Custom fields let you define those once, attach them to every account in your company, and reference them in account forms, the Accounts list filters, saved filters, campaign bulk-add, imports and exports.

Custom fields are scoped to a project — a bike-shop project’s fleet_size is invisible to a podcast-directory project; neither project’s schema pollutes the other’s data.

TypeWhat it storesExample
textShort single-line stringLicense number
textareaLong multi-line stringNotes about the location
numberInteger or decimalNumber of franchises
dateYYYY-MM-DDContract renewal date
booleantrue / falseHas loyalty program?
selectOne value from a listTier: Gold / Silver / Bronze
multiselectMany values from a listLanguages spoken
urlValidated URLReviews page

text, textarea and url values hold at most 10,000 characters. A longer value is refused with an error on that field (“At most 10,000 characters”), so paste long documents somewhere else and link to them. Every account carries its custom fields in each list and export, so a cap keeps those fast.

A quick decision guide for which type to reach for:

You want to capture…Use
One label from a finite list (Gold / Silver / Bronze)select
Many labels from a finite list (multi-tag)multiselect
A yes/no flagboolean
A count you’ll want to filter or sort bynumber
A specific datedate
A clickable URLurl
A short opaque string (license number, account ref)text
A long free-form blobtextarea (but consider: would notes on the Account itself do?)

Picking the most-restrictive type that fits makes the field usable in saved filters with the right operators. text is the catch-all, but a select would let you filter cleanly while text only supports substring matching.

From Settings → Custom fields → Add field, each definition carries:

PropertyWhat it does
KeyMachine name. Lowercase, snake_case, starts with a letter (regex ^[a-z][a-z0-9_]{0,79}$). Shows up in filter expressions as cf.<key>.
LabelHuman-readable name shown on account forms (e.g. “Fleet size”).
TypeOne of the eight above.
Product scopeOptional. Leave it on Project-wide and the field shows on every account; pick a product and the field is grouped under that product on the account screens. Handy when a field only makes sense for one product line.
OptionsRequired for select / multiselect. The exact strings counted as valid values.
Deep-link templateurl fields only — turns the field into a one-click link into an external system. See Deep links into external systems.
Help textOne-line hint shown next to the input on the account form.
RequiredWhen on, account create / edit forms reject submissions that don’t fill this field. Partial updates skip the check, so a re-import that omits the field doesn’t fail.
OrderDisplay position on account forms. Lower numbers appear first.

The schema is per-project, not global. A field defined under Bike shops doesn’t show up under Radio stations — each project’s fields are isolated, even inside one organization. Within a project you can narrow a field further to a single product with Product scope — the value still lives on the account, the product is just how it’s grouped in the UI.

Two paths an operator can use:

  1. The account form — every defined field shows up in the Custom fields section of the new-account form and in the Custom fields card on the account page’s Profile tab, in the order you set. Validates on save against the declared type.
  2. CSV / XLSX import — map a column to a custom field key in the import wizard’s column-mapping step. The static-values trick from Import accounts also works here: set a batch-wide custom-field value (e.g. every row in this file is cf.source_list = "Trade show 2026") without needing a column for it.

Auto-enrichment doesn’t fill custom fields. The discover-website + scrape + language-detect pipeline only populates the standard fields (website, language). If you need a custom field populated automatically, fill it on import or on the account form.

A url field can be more than a place to paste a link. Give it a deep-link template and LeadHunter builds a one-click link into your CRM, billing system, admin panel — anywhere an account also lives — without anyone pasting a URL per row.

You write the template once, with {placeholders} LeadHunter fills in per account:

https://crm.example.com/contact/{imported_id}
https://your-admin.example.com/client/{value}

Three kinds of placeholder:

PlaceholderFilled with
{value}A value the operator types into this field on the account (use this once per template).
{cf.<key>}The value of another custom field on the same account.
Account fieldsThe account’s own data — {imported_id}, {id}, {website}, {email}, {phone}, {name}, {city}, {country}, {google_place_id}.

The standout is {imported_id}. When you import a list, LeadHunter remembers each row’s original ID from the source system. A template like https://crm.example.com/contact/{imported_id} then deep-links every imported account straight back into that system — no per-account typing at all. That’s the quickest way to make LeadHunter a launchpad into the tools your team already uses.

Set it up in Settings → Custom fields → Add field:

  1. Choose type URL.
  2. Fill Deep-link template with your URL and placeholders. A live preview shows what the link will look like.
  3. Optionally set a Link label (e.g. “Open in CRM”, “Enacast admin”) — that’s the text shown instead of a raw URL. It falls back to the field’s label.
  4. Optionally set Product scope if the link only applies to one product.

On each account, the field renders as a labelled, clickable link that opens in a new tab. If the template needs a {value}, you’ll see an input to type it; if it’s built entirely from account data, there’s nothing to fill — the link just appears. When a referenced piece of data is missing on an account (e.g. a row with no imported ID), no broken link is shown for that account.

A few practical notes:

  • Safe by design — templates must point at an http(s) web address with a fixed host (e.g. https://crm.example.com/…); placeholders go in the path or query string, not the host. Values you type are encoded into the link so odd characters can’t break it.
  • Plain URL fields still work — leave the template blank and the field behaves like before: paste a whole URL and it renders as a link.
  • Exports include the built link — when you download a campaign’s accounts as CSV, a deep-link column carries the finished URL, ready to hand to another tool.
  • One manual value per field — if you need two different external IDs on the same account, make two fields.

The other direction shipped: webhooks. Deep links take you into the other system; outbound webhooks send data the other way — notifying your external system the moment an account is created, changes status, or replies. Pulling state back (full two-way sync) is the remaining roadmap step.

Custom fields are for segmenting and targeting: they decide which accounts go into a list, a ranking run or a campaign. The fit score itself doesn’t read them today. The scoring model reads the account’s name, location, business type, specialization, website and its text, language, email and phone, and the start of its notes.

So when a custom field’s value should move the score (a bike shop with a workshop is a better fit), either filter on it before you rank or score (rank only the shops where cf.service_offered is true), or say it in a field the model reads, such as the business type or specialization. See Account → Notes.

Reference custom fields in saved filters with the cf. prefix:

match: all
conditions:
country = Spain
cf.tier in [Gold, Platinum]
cf.contract_renewal_date lt 2026-06-30

Custom fields support the same operators as built-ins, modulo the field type — gt / lt apply only to numbers and dates; is_true / is_false only to booleans; contains differs between text (substring) and multiselect (array membership). See Build a saved filter for the full operator matrix and worked examples.

When two accounts merge, custom fields are unioned:

  • text / textarea / number / date / url / select / boolean — if only one row has a value, that value sticks; if both have values, the survivor’s wins but the absorbed row’s value is recorded in merge_history so you can read what was overridden.
  • multiselect — union of both arrays. No loss.

Nothing is silently dropped, even on a conflict.

ActionEffect
Add a fieldInstant. Existing accounts immediately gain the field with a null value; new accounts pick it up on the next form load.
Rename label or help textInstant. Filters and forms keep working — they reference the key, not the label.
Rename a keyNot safe. References in saved filters and import mappings break silently. Prefer creating a new field with the new key, backfilling values, then deleting the old one.
Delete a fieldRemoves it from the schema (stops showing in forms; rejected by the import wizard). The values stay on existing accounts in the underlying JSON blob — they just become invisible. Cheap to re-add; expensive to recover deleted history. If you’re sure you want the values gone too, ask support for a one-off cleanup.
  • Personal data on individual people — names, emails, phone numbers of specific contacts. Those belong on the Contact model, not on the Account’s custom fields.
  • High-cardinality free text you never plan to filter on. A 10KB description of every account belongs in notes, not in a textarea custom field — custom fields are meant to be queryable, and JSON-indexed long text doesn’t query efficiently.
  • Anything that’s the same for every account — that’s a project-level setting, not a field on each row.
  • Anything you’ll want to aggregate across the whole database — define a custom field that filters cleanly (number, date, select) and use saved filters for the aggregation; the saved-filter reach estimator is the closest thing to a count-by query in LeadHunter today.

For a project running outbound to bike shops, useful custom fields might be:

FieldTypeWhy
fleet_sizenumberTarget by store size. “Shops with ≥20 bikes in stock” is a meaningful audience.
brands_carriedmultiselect, options = the brands you care about”Shops that carry Trek but not Specialized” is a meaningful slice.
service_offeredbooleanSome products only fit shops with a workshop.
contract_renewal_datedateFor existing customers — filter for “renewing in the next 90 days”.
loyalty_program_urlurlUseful for partnership campaigns; one click from the row.

A saved filter combining country = Spain + cf.fleet_size gte 20 + cf.brands_carried contains Trek + status = prospect gives you the audience for next quarter’s premium-Trek-dealer campaign.

Worked example 2 — a podcast / radio-station directory

Section titled “Worked example 2 — a podcast / radio-station directory”

A project running outbound to media outlets might define:

FieldTypeWhy
formatselect, options = news, music, talk, mixedThe audience is different per format: filter on it before you rank.
weekly_audiencenumberKeep only stations above an audience floor (cf.weekly_audience gte 5000).
genresmultiselect, options = the music genres you care about”Rock and electronic, but not classical” is a real targeting move.
accepts_sponsorshipbooleanHard prerequisite — set it after the first conversation.
frequency_mhznumberGeographic + technical filter for FM-only campaigns.

For a “podcast advertising platform for music shows” product, filter the Accounts list to cf.genres in your genres and cf.weekly_audience above your floor, then Rank that list: the model scores only the stations that already pass the hard requirements, using what it reads on each station’s website.