Return data action returns single item but null for a list of the same data type

I’m using the new Trigger a backend custom event action from the frontend, and inside that backend custom event I’m using the Return data action to send search results back to the page.

In the Return data action I’ve set two return values, both built from the same search on the same data type (Dublicate_Questions):

  • Single QuestionSearch for Dublicate_Questions:first item
  • All QuestionsSearch for Dublicate_Questions (the full list)

On the frontend I store both into custom states.

The problem: the Single Question value comes through fine, but All Questions comes back null/empty every time.

What’s confusing me: it’s the exact same search inside the exact same action. If :first item is returning a value, the search clearly has at least one result — so the list should return at least one item too, right? But the list is just empty

A few things I’ve already checked / ruled out:

  • Privacy rules aren’t the cause — I’ve ignored privacy rules on the backend, and that’s exactly why the single value works
  • It’s not a search-returns-nothing issue, since the single-item return proves the search has results.

Has anyone hit this with the new Return data action? Is returning a list type from Return data supported the same way a single thing is, or is there a known limitation / different setup needed for list return values? Any pointers appreciated.

You say exact same search, but just wanna double check: you’re using the exact same constraints on both?

The workflow can ignore privacy rules, but the return data will still honour it (you probably knew this). But if you search for an existing thing by unique ID, it’s technically a lookup, and not a search. In that case, it will probably return the thing.

Can you explain the differences between how Bubble handles lookups by just UID versus other constraints? The way you worded this makes it seem like 2 different methods even though they are the same Bubble operator.

@ihsanzainal84

Yeah, when you do a search and your only constraint is unique ID + :first item, Bubble treats it as a direct fetch instead of a general search. In essence, Do a search for protects a thing from being discovered in a search, but if you give it the unique ID it will return the thing. Fields will still be protected, so with proper privacy rules, all it does is confirm the thing exists in the db.

Mostly this is fine, but it does open up for one potential vulnerability. If you set up a Make changes to a thing, and search for the thing by UID, and you are relying on Can be found in searches to protect that thing (i.e. you are relying on Bubble to return an empty record), then Bubble will make changes to it regardless.

That’s a serious bug IMO. Rare, but it can lead to unexpected CRUD and be hard to spot unless builders already know this issue exists.

Yea this is known behaviour but to be fair Bubble should change this to provide more clarity for both use and security.

Might even be better served as 2 actions (or a sub-note/action or something):

  • Do a search for (respects privacy)
  • Get a thing (doesn’t respect privacy for finding the thing, still respects field privacy)

I stumbled upon it when I wrote my security book, and I’ve talked to them a couple of times. They don’t consider it a bug, but I agree it’s not very obvious.

Thanks @petter

Yes, both return values are using the same constraints.

To clarify, the single value is coming from the same search expression as the list return, just with :first item applied.

For example:

Single Question:

All Questions:

That’s why I’m confused. If the search returns a first item, I’d expect the list version of that same search to contain at least that item.

Regarding privacy rules, are you suggesting that Return Data may evaluate a single Thing returned from a search differently than a list of Things returned from the same search? If so, that would explain what I’m seeing.

I just did a quick test, and it doesn’t seem to be privacy rules. When I disallow everyone else/search, it returns nothing in both single and multiple records. When allowed, it returns both.

Could it be related to the parameter? Perhaps it’s empty in one version?

The parameter is same for both cases.

It is related to Privacy Rules, I am not sure about “How” though.

I just selected ‘Find this in Searches’ [1] and now both Single and list is returning values. Earlier I had selected only ‘View’ [2].

Hmm, just realized I made a mistake when texting: I forgot to check Ignore privacy rules :man_facepalming:

I’m getting the same as you now:

  • Search and view fields allowed: returns both list and single record
  • Only view fields allowed: returns only the single record

It’s a bit inconsistent imo. For the list, it recognizes the search and blocks it, but for the single I’m guessing it reverts to a lookup again, even if the result came from a non-uid search.

Thanks again for checking.

This still feels like a bug or at least an inconsistency to me!

I agree it’s inconsistent, and easy to get wrong. I’ve pinged one of the people who worked on it to see if we can get to the bottom of whether this is intentional or not.

So the running theory with OP’s particular issue is that adding :first item is switching the search to a lookup and the “not bug” is affecting OP’s results?

Yes, but not on the search in the workflow. I think return data sends the UID to the client, and the client then performs the search (by UID) and thus overriding privacy rules.

So the first search isn’t a lookup (but privacy rules are overridden by the trigger action), but the client has to search a second time, and this time it’s UID based.

It would explain the logic, but it’s not intuitive.

Hey, I’m running into the same issue where I cannot return lists under the new return data action, and it’s definitely a privacy issue.

Privacy open: returns all results
Privacy closed : returns null/othing

@nithinsudheer7 , where you able to resolve this?

So I’ve talked to Bubble to confirm this is the intended behavior, and it is. While it makes sense from a technical perspective, I don’t think it’s intuitive, so I’ve left notes in relevant places in the docs explaining this.

So to those that have followed this thread or stumble upon it, here’s the logic:

  • All these points are relevant only when you override privacy rules when triggering a backend custom event, and then use the Return data action.
  • When you override privacy rules, as you would expect, any searches within the custom event will return results with full access. The same is true for reading fields.
  • When you return data, the behavior is as follows:
    • Returning a list: Bubble will send the query to the client, and the search is performed again as the current user. Disabling Find this in searches means the search will return no records, and no unique IDs.
      • If you use the :make static operator, Bubble will return a list of unique IDs.
    • Returning a thing: Bubble sends the unique ID of the thing to the client. Bubble performs a lookup on that unique ID, and that will always return a result. This is the same mechanic as when you store a thing in a field on another thing: the unique ID is visible, even if no fields are visible. So fields need to be protected with privacy rules.

So the important thing to keep in mind is that:

  1. Searching for a thing by UID will always yield a result, regardless of privacy rules (fields are still protected). This has always been true.
  2. Return data returns a UID, which is then fetched (mget). Technically, it returns the record, but fields remain protected.
    1. Always protect fields with privacy rules
    2. Keep in mind that the value is not empty, even if no fields are displayed. So using an :is empty operator on the returned single value will always return a no.

Thanks @petter , do you know why this wouldn’t allow returning data (text) from that list as a subset? (E.g each item’s name)

EDIT: This whole thing made me curious, so I had to dig some deeper. The logic I describe below isn’t entirely accurate, since it’s contradictory in two places:

  • if Return data always sent the query, and not the value, then the :first item shouldn’t yield a result.
  • If return data didn’t override privacy rules, it shouldn’t be able to send back a unique ID

The solution instead has to do with unique IDs, and dynamic versus static lists. I’ve written an article where I go deeper into it here.

OUTDATED SOLUTION:

Just did some testing, and the whole thing is a bit confusing. Here’s what I’ve found:

  • Assume in this case that Override privacy rules is checked on the trigger action

  • Also assume that the Find this in searches privacy rule is disabled for the current user. I.e. the search described below should yield no results.

  • If you place a search directly in a Return data return value, it will literally return the search – the query itself, not the result of the search.

    • The list will be empty for the user, since they have no access to the data
    • Even if you pass a different type of data, such as Data type’s:each datatype’s name it will not produce a result.
    • Conclusion: privacy rules are not overridden in the Return data action.
  • However, if you place the search in a different step (such as inserting a blank Make changes to a list of things), and then refer to that step using Result of step X

    • Privacy rules for searching are overriden, and the return action will return the full list of unique IDs
    • Conclusion: Privacy rules for search are overridden if the search for the list comes from a different step.

The main learning is that return data action returns exactly and literally the dynamic expression you give to it. If it contains a Do a search for, then that search query is returned, and run from the client with the current user’s privileges (no results). This is the case even if you try to send a different value type, such as a text: the return data action as a general rule does not override privacy rules.

However, if you send a list from a previous step where privacy rules are overriden, then Bubble will make no attempt to stop that list from being sent to the client.

I can kind of see the logic from Bubble’s perspective, and how data is passed, but it’s not how I expected this to work.

@DjackLowCode so in your case, if you move the search to a previous step in the workflow, you should get the expected result.

Thanks @petter ! This makes sense :slight_smile:

This could potentially be hangups from refactoring for securities sake (this topic: Bubble.io will implement return backend data to frontend soon? - #7 by th18)